diff --git a/core-customize/build.gradle.kts b/core-customize/build.gradle.kts index c264117..42740f5 100644 --- a/core-customize/build.gradle.kts +++ b/core-customize/build.gradle.kts @@ -118,13 +118,47 @@ if (project.hasProperty("CXDEV_ARTEFACT_BASEURL") && project.hasProperty("CXDEV_ val COMMERCE_VERSION = CCV2.manifest.effectiveVersion tasks.register("downloadPlatform") { - src(BASEURL + "/commerce/hybris-commerce-suite-${COMMERCE_VERSION}.zip") - dest(file("${dependencyDir}/hybris-commerce-suite-${COMMERCE_VERSION}.zip")) - header("Authorization", "Basic ${AUTHORIZATION}") + val targetFile = file("${dependencyDir}/hybris-commerce-suite-${COMMERCE_VERSION}.zip") + + src("$BASEURL/commerce/hybris-commerce-suite-${COMMERCE_VERSION}.zip") + dest(targetFile) + + header("Authorization", "Basic $AUTHORIZATION") + overwrite(false) tempAndMove(true) onlyIfModified(true) useETag(true) + + doFirst { + logger.lifecycle("=== Download Platform Debug ===") + logger.lifecycle("Source URL: $BASEURL/commerce/hybris-commerce-suite-${COMMERCE_VERSION}.zip") + logger.lifecycle("Destination: ${targetFile.absolutePath}") + logger.lifecycle("Destination exists: ${targetFile.exists()}") + logger.lifecycle("Dependency dir: ${file(dependencyDir).absolutePath}") + + if (file(dependencyDir).exists()) { + logger.lifecycle("Directory content:") + file(dependencyDir).listFiles()?.forEach { + logger.lifecycle(" - ${it.name}") + } + } + } + + doLast { + logger.lifecycle("=== Download completed ===") + logger.lifecycle("File exists after download: ${targetFile.exists()}") + logger.lifecycle("File size: ${targetFile.length()}") + + logger.lifecycle("Dependency dir: ${file(dependencyDir).absolutePath}") + + if (file(dependencyDir).exists()) { + logger.lifecycle("Directory content:") + file(dependencyDir).listFiles()?.forEach { + logger.lifecycle(" - ${it.name}") + } + } + } } tasks.named("bootstrapPlatform") { diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/extensioninfo.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/extensioninfo.xml index 2a35569..70b864f 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/extensioninfo.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/extensioninfo.xml @@ -1,6 +1,6 @@ - + diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/external-dependencies.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/external-dependencies.xml index b86b779..e4394ae 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/external-dependencies.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevbackoffice/external-dependencies.xml @@ -3,7 +3,7 @@ 4.0.0 me.cxdev cxdevbackoffice - 5.0.1 + 5.0.2 jar diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/extensioninfo.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/extensioninfo.xml index 116068c..b224d53 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/extensioninfo.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/extensioninfo.xml @@ -1,7 +1,7 @@ + name="cxdevenvconfig" version="5.0.2" usemaven="true"> diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/external-dependencies.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/external-dependencies.xml index e3c9db1..d3d7650 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/external-dependencies.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevenvconfig/external-dependencies.xml @@ -3,7 +3,7 @@ 4.0.0 me.cxdev cxdevenvconfig - 5.0.1 + 5.0.2 jar diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/extensioninfo.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/extensioninfo.xml index f663b49..4c074ef 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/extensioninfo.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/extensioninfo.xml @@ -1,6 +1,6 @@ - + diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/external-dependencies.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/external-dependencies.xml index 82f7db5..9c34a07 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/external-dependencies.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/external-dependencies.xml @@ -3,7 +3,7 @@ 4.0.0 me.cxdev cxdevproxy - 5.0.1 + 5.0.2 jar diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/project.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/project.properties index 58b6d81..8a04e3b 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/project.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/project.properties @@ -9,15 +9,14 @@ cxdevproxy.enabled=false # SSL / Keystore Configuration # Defines the keystore details used for securing the proxy server via HTTPS. -cxdevproxy.ssl.enabled=false +cxdevproxy.ssl.enabled=true cxdevproxy.ssl.keystore.path=${HYBRIS_CONFIG_DIR}/../../../certificates/local.cxdev.me.p12 cxdevproxy.ssl.keystore.password=123456 cxdevproxy.ssl.keystore.alias=local.cxdev.me # Undertow Server Binding # Defines the network interface, hostname, and port the embedded proxy will listen on. -cxdevproxy.server.bindaddress=127.0.0.1 -cxdevproxy.server.protocol=http +cxdevproxy.server.bindaddress=0.0.0.0 cxdevproxy.server.hostname=local.cxdev.me cxdevproxy.server.port=8080 @@ -38,7 +37,7 @@ cxdevproxy.proxy.ui.messages.codeasfallback=true # CX Dev Proxy - Frontend Routing (Target) # ----------------------------------------------------------------------- # Target configuration for routing frontend requests (e.g., local Angular dev server). -cxdevproxy.proxy.frontend.protocol=https +cxdevproxy.proxy.frontend.protocol=http cxdevproxy.proxy.frontend.hostname=localhost cxdevproxy.proxy.frontend.port=4200 diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy-spring.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy-spring.xml index 2abc298..974e8b9 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy-spring.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy-spring.xml @@ -10,7 +10,6 @@ - diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/config/cxdevproxy-interceptor-spring.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/config/cxdevproxy-interceptor-spring.xml index 9b03cf7..071694e 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/config/cxdevproxy-interceptor-spring.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/config/cxdevproxy-interceptor-spring.xml @@ -2,13 +2,6 @@ - - - - - - - diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-backend-rules.groovy b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-backend-rules.groovy index 8235f6c..aa7f907 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-backend-rules.groovy +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-backend-rules.groovy @@ -1,5 +1 @@ -return [ - interceptor() - .constrainedBy( isMethod("GET"), pathMatches("/**/carts/current") ) - .perform( jsonResponse('{"type": "cartWsDTO", "totalItems": 5}') ) -] \ No newline at end of file +return [] \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-frontend-rules.groovy b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-frontend-rules.groovy index 43e25a7..aa7f907 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-frontend-rules.groovy +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/resources/cxdevproxy/rulesets/cxdevproxy-frontend-rules.groovy @@ -1,3 +1 @@ -def interceptor = [] -interceptor << forwardedHeaders -return interceptor \ No newline at end of file +return [] \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/src/me/cxdev/commerce/proxy/livecycle/UndertowProxyManager.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/src/me/cxdev/commerce/proxy/livecycle/UndertowProxyManager.java index 22af03d..649eabf 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/src/me/cxdev/commerce/proxy/livecycle/UndertowProxyManager.java +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/src/me/cxdev/commerce/proxy/livecycle/UndertowProxyManager.java @@ -62,28 +62,28 @@ public class UndertowProxyManager implements SmartLifecycle, InitializingBean, D private static final Logger LOG = LoggerFactory.getLogger(UndertowProxyManager.class); // Spring Injected Properties - private boolean enabled; + private boolean enabled = false; // SSL Properties - private boolean sslEnabled; + private boolean sslEnabled = true; private String sslKeystorePath; private String sslKeystorePassword; private String sslKeystoreAlias; // Server Binding Properties - private String serverBindAddress; - private String serverHostname; - private String serverProtocol; - private int serverPort; + private String serverBindAddress = "0.0.0.0"; + private String serverHostname = "localhost"; + private String serverProtocol = "https"; + private int serverPort = 8080; // Proxy Target Properties - private String frontendProtocol; - private String frontendHostname; - private int frontendPort; + private String frontendProtocol = "http"; + private String frontendHostname = "localhost"; + private int frontendPort = 4200; private String frontendRulesFilePath; - private String backendProtocol; - private String backendHostname; - private int backendPort; + private String backendProtocol = "https"; + private String backendHostname = "localhost"; + private int backendPort = 9002; private String backendRulesFilePath; private String backendContexts; @@ -198,28 +198,33 @@ public void start() { return; } - if (!sslEnabled) { - this.serverProtocol = "http"; - LOG.info("SSL is disabled. Forcing server protocol to 'http'."); - } - + this.serverProtocol = sslEnabled ? "https" : "http"; LOG.info("Starting embedded Undertow proxy (Protocol: {})...", this.serverProtocol); try { + XnioSsl frontendSslContext = "https".equalsIgnoreCase(frontendProtocol) ? createTrustAllXnioSsl(frontendHostname) : null; String frontendUrl = frontendProtocol + "://" + frontendHostname + ":" + frontendPort; LoadBalancingProxyClient frontendClient = new LoadBalancingProxyClient() - .addHost(new URI(frontendUrl), createTrustAllXnioSsl(frontendHostname)) + .addHost(new URI(frontendUrl), frontendSslContext) .setConnectionsPerThread(20); + XnioSsl backendSslContext = "https".equalsIgnoreCase(backendProtocol) ? createTrustAllXnioSsl(backendHostname) : null; String backendUrl = backendProtocol + "://" + backendHostname + ":" + backendPort; LoadBalancingProxyClient backendClient = new LoadBalancingProxyClient() - .addHost(new URI(backendUrl), createTrustAllXnioSsl(backendHostname)) + .addHost(new URI(backendUrl), backendSslContext) .setConnectionsPerThread(20); - HttpHandler baseFrontendHandler = ProxyHandler.builder().setProxyClient(frontendClient).build(); + HttpHandler baseFrontendHandler = ProxyHandler.builder() + .setProxyClient(frontendClient) + .setReuseXForwarded(true) + .build(); HttpHandler finalFrontendHandler = applyRules(frontendHandlersRef.get(), baseFrontendHandler); - HttpHandler baseBackendHandler = ProxyHandler.builder().setProxyClient(backendClient).setMaxRequestTime(30000).build(); + HttpHandler baseBackendHandler = ProxyHandler.builder() + .setProxyClient(backendClient) + .setMaxRequestTime(30000) + .setReuseXForwarded(true) + .build(); HttpHandler finalBackendHandler = applyRules(backendHandlersRef.get(), baseBackendHandler); List activeBackendContexts = determineBackendContexts(); @@ -452,10 +457,6 @@ public void setServerHostname(String serverHostname) { this.serverHostname = serverHostname; } - public void setServerProtocol(String serverProtocol) { - this.serverProtocol = serverProtocol; - } - public void setServerPort(int serverPort) { this.serverPort = serverPort; } diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/testsrc/me/cxdev/commerce/proxy/livecycle/GroovyRuleEngineServiceTest.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/testsrc/me/cxdev/commerce/proxy/livecycle/GroovyRuleEngineServiceTest.java index 7fb0c8c..d0c4b2d 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/testsrc/me/cxdev/commerce/proxy/livecycle/GroovyRuleEngineServiceTest.java +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevproxy/testsrc/me/cxdev/commerce/proxy/livecycle/GroovyRuleEngineServiceTest.java @@ -54,7 +54,6 @@ void setUp() { // Setup Spring ApplicationContext mocks with custom bean names to test the prefix stripping Map interceptors = new HashMap<>(); - interceptors.put("cxdevproxyInterceptorForwardedHeaders", mockInterceptor); interceptors.put("customInterceptorWithoutPrefix", mockInterceptor); // Edge case Map conditions = new HashMap<>(); @@ -129,8 +128,7 @@ void testEvaluateScript_WithValidScriptAndBindings(@TempDir Path tempDir) throws assertNotNull(result, "Result list should not be null"); assertEquals(2, result.size(), "Script should return exactly two interceptors"); - // Assert that the first returned interceptor is the exact mock instance we injected, - // proving that 'forwardedHeaders' was successfully bound to 'cxdevproxyInterceptorForwardedHeaders' + // Assert that the first returned interceptor is the exact mock instance we injected assertEquals(mockInterceptor, result.get(0)); } diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/extensioninfo.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/extensioninfo.xml index a0b8b68..16a87fc 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/extensioninfo.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/extensioninfo.xml @@ -1,6 +1,6 @@ - + diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/external-dependencies.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/external-dependencies.xml index c5261aa..a0c454c 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/external-dependencies.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevreporting/external-dependencies.xml @@ -3,7 +3,7 @@ 4.0.0 me.cxdev cxdevreporting - 5.0.1 + 5.0.2 jar diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/README.md b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/README.md index 57f1f7f..ceb90ab 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/README.md +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/README.md @@ -121,16 +121,49 @@ the configuration file `systemsetup-spring.xml` and inspect the project data imp ## Centralized HtmlEmail handling -The main purpose of these services is, to provide a simple way of sending emails to the customers, without making use of the `CMSComponents` -like with the standard way of mailing within the SAP Commerce Cloud, provided by the `acceleratorservices` extension. The implementation -makes use of the [Thymeleaf rendering engine](https://www.thymeleaf.org/), i.e. you are able to define your mails as thymeleaf templates -and provide localized messages to it. - -The `HtmlEmailGenerator` services (registered as bean with name `thymeleafHtmlEmailGenerator` and alias `htmlEmailGenerator`) should be used -to create `HtmlEmail` objects, whenever you want to send an email, e.g. from Workflows or from EventListeners. The class provides a simple -and an enhanced mechanism to create `HtmlEmail` objects. The simple way takes a `String` as a body and sets it as HTML body for the email. -The enhanced mechanism takes a template name and context parameters. The template is resolves from the classpath and the template engine is -executed with the provided context parameters. +The main purpose of these services is to provide a simple way of sending emails without relying on CMSComponents, as +required by the standard SAP Commerce Cloud acceleratorservices approach. The implementation uses the Thymeleaf +rendering engine. + +### Template resolution + +Email templates are stored as localized `ThymeleafEmailTemplate` items in the database and can be managed via +Backoffice (under *CX DEV Tools → Email*) or Impex. + +At runtime, `EmailTemplateResolverService` (impl: `DatabaseEmailTemplateResolverService`) resolves the matching +`ThymeleafEmailTemplate` by `code` and locale, and passes the localized HTML content to `HtmlEmailBuilder` for +Thymeleaf rendering. + +### Impex import + +Templates can be created or updated via Impex: + + ```impex + INSERT_UPDATE ThymeleafEmailTemplate; code[unique = true]; template[lang = de] ; template[lang = en] + ; contact ; " + + +

+

+ + " ; + ``` + +**Impex workaround for Thymeleaf expressions:** Impex interprets `${...}` as its own variable syntax. To work around +this: + +- Define the macro `$d=$` at the top of the Impex file. This causes `$d{...}` to be resolved to `${...}` during + import. +- Use `$d{...}` instead of `${...}` for all Thymeleaf expressions in the file. +- Escape all double quotes inside HTML values by doubling them (`"` → `""`). + + +### HtmlEmailGenerator (programmatic use) + +The `HtmlEmailGenerator` bean (`thymeleafHtmlEmailGenerator` / alias `htmlEmailGenerator`) remains available for +programmatic email creation, e.g. from Workflows or EventListeners. It provides a simple mode (plain HTML string as +body) and an enhanced mode (template code + locale + context parameters). + ### How to activate and use diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/extensioninfo.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/extensioninfo.xml index aaa5c53..7f04960 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/extensioninfo.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/extensioninfo.xml @@ -1,7 +1,7 @@ + name="cxdevtoolkit" version="5.0.2" usemaven="true"> diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/external-dependencies.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/external-dependencies.xml index 72e225e..fbd076c 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/external-dependencies.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/external-dependencies.xml @@ -3,7 +3,7 @@ 4.0.0 me.cxdev cxdevtoolkit - 5.0.1 + 5.0.2 jar diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/project.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/project.properties index bd2e400..cc80238 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/project.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/project.properties @@ -87,30 +87,6 @@ cxdevtoolkit.impeximport.environment.importtestdata=false # update.executeProjectData.extensionName.list=cxdevtoolkit ############################################################################## - -############################################################################## -# Html Email Service configuration -# -# The HtmlEmailService requires all templates to be stored within the following class paths: -# cxdevtoolkit/email-templates/html/** -# cxdevtoolkit/email-templates/text/** -# -# For localization of the emails, message bundles are preconfigured. Unfortunately, a message source cannot -# hold wildcards and, therefore, this extension has 5 prepared configuration paths that are resolved during -# system startup. If you want to contribute to the message bundle for email templates, please take one of the -# properties below, and change the path accordingly. The order is important, see note below! -# -# Note: You cannot simply add your messages to a file called cxdevtoolkit/email-templates/messages, because the message -# source will stop file resolving, after the first match, ie. a file already existing in a previous message -# source will stop the resolver from loading your keys from your local file with the same name. -############################################################################## -cxdevtoolkit.htmlEmailService.configuration.messagebundle1=classpath*:cxdevtoolkit/email-templates/messages -cxdevtoolkit.htmlEmailService.configuration.messagebundle2=classpath*:cxdevtoolkit/email-templates/messages -cxdevtoolkit.htmlEmailService.configuration.messagebundle3=classpath*:cxdevtoolkit/email-templates/messages -cxdevtoolkit.htmlEmailService.configuration.messagebundle4=classpath*:cxdevtoolkit/email-templates/messages -cxdevtoolkit.htmlEmailService.configuration.messagebundle5=classpath*:cxdevtoolkit/email-templates/messages - - ############################################################################## # Html Email Service Fake configuration # diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-config.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-config.xml index b19c6f0..41af466 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-config.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-config.xml @@ -18,6 +18,9 @@ + + + @@ -79,4 +82,34 @@ + + + + + + 'Email Template' + (code != null ? '' + ': ' + code : '') + + + + + + + + + + + + + + + + rows + 15 + + + + + + + \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_de.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_de.properties index 84e0a38..ee61ccf 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_de.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_de.properties @@ -1,2 +1,5 @@ cxdevtools_treenode_main=CX DEV Tools -cxdevtools_treenode_fakemail=Fake Mails \ No newline at end of file +cxdevtools_treenode_fakemail=Fake Mails +cxdevtools_treenode_email=E-Mail + +cxdevtools_section_ThymeleafEmailTemplate_content=Template Inhalt \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_en.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_en.properties index 84e0a38..d265183 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_en.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-backoffice-labels/labels_en.properties @@ -1,2 +1,5 @@ cxdevtools_treenode_main=CX DEV Tools -cxdevtools_treenode_fakemail=Fake Mails \ No newline at end of file +cxdevtools_treenode_fakemail=Fake Mails +cxdevtools_treenode_email=Email + +cxdevtools_section_ThymeleafEmailTemplate_content=Template Content \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-items.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-items.xml index 8323df6..34168b1 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-items.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-items.xml @@ -13,5 +13,39 @@ + + + + + + + + + + + HYBRIS.LONG_STRING + + + TEXT + + + CLOB + + + TEXT + + + NCLOB + + + + + + + + + + + diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-spring.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-spring.xml index c9db63e..6422858 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-spring.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit-spring.xml @@ -6,4 +6,15 @@ + + + + + + + + + +
\ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit/thymeleafemails-spring.xml b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit/thymeleafemails-spring.xml index ea3def5..fbbe24a 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit/thymeleafemails-spring.xml +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/cxdevtoolkit/thymeleafemails-spring.xml @@ -53,15 +53,6 @@ - - - ${cxdevtoolkit.htmlEmailService.configuration.messagebundle1} - ${cxdevtoolkit.htmlEmailService.configuration.messagebundle2} - ${cxdevtoolkit.htmlEmailService.configuration.messagebundle3} - ${cxdevtoolkit.htmlEmailService.configuration.messagebundle4} - ${cxdevtoolkit.htmlEmailService.configuration.messagebundle5} - - diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_de.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_de.properties index 02ac30d..1d58e82 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_de.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_de.properties @@ -1,4 +1,11 @@ -type.LocallyStoredEmail.name=Lokale Email -type.LocallyStoredEmail.description=Lokal gespeicherte Email durch einen Fake Service, anstatt sie via SMTP zu versenden. +type.LocallyStoredEmail.name=Lokale E-Mail +type.LocallyStoredEmail.description=Lokal gespeicherte E-Mail durch einen Fake Service, anstatt sie via SMTP zu versenden. type.LocallyStoredEmail.sender.name=Absender -type.LocallyStoredEmail.recipients.name=Empfänger \ No newline at end of file +type.LocallyStoredEmail.recipients.name=Empf\u00E4nger + +type.ThymeleafEmailTemplate.name=E-Mail-Template +type.ThymeleafEmailTemplate.description=Speichert lokalisierte HTML-Vorlagen, die von der Thymeleaf-Engine f\u00FCr E-Mails verwendet werden. +type.ThymeleafEmailTemplate.code.name=Template-Name +type.ThymeleafEmailTemplate.code.description=Eindeutige Kennung zur Aufl\u00F6sung der Vorlage (z. B. 'contact', 'registration') +type.ThymeleafEmailTemplate.template.name=HTML-Vorlage +type.ThymeleafEmailTemplate.template.description=Roher HTML-Code, der das Thymeleaf-Markup f\u00FCr den E-Mail-Body enth\u00E4lt \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_en.properties b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_en.properties index 4b5b9a7..1b34696 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_en.properties +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/resources/localization/cxdevtoolkit-locales_en.properties @@ -1,4 +1,11 @@ type.LocallyStoredEmail.name=Local Email type.LocallyStoredEmail.description=Email stored locally by a fake service instead of sending them via SMTP. type.LocallyStoredEmail.sender.name=Sender -type.LocallyStoredEmail.recipients.name=Recipients \ No newline at end of file +type.LocallyStoredEmail.recipients.name=Recipients + +type.ThymeleafEmailTemplate.name=Email Template +type.ThymeleafEmailTemplate.description=Stores localized HTML templates processed by the Thymeleaf engine for emails. +type.ThymeleafEmailTemplate.code.name=Template Name +type.ThymeleafEmailTemplate.code.description=Unique identifier used to resolve the template (e.g., 'contact', 'registration') +type.ThymeleafEmailTemplate.template.name=HTML Template +type.ThymeleafEmailTemplate.template.description=Raw HTML code containing the Thymeleaf markup for the email body \ No newline at end of file diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailBuilder.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailBuilder.java index e223b11..10e04b9 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailBuilder.java +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailBuilder.java @@ -34,7 +34,7 @@ static HtmlEmailBuilder withHtmlEmailGenerator(HtmlEmailGenerator generator) { private String subject; private String body; - private String templateName; + private String templateHtml; private Map contextParameters; private Locale templateLocale; @@ -114,9 +114,8 @@ public HtmlEmailBuilder body(String body) { return this; } - public HtmlEmailBuilder template(String template, Locale locale) { - this.templateName = template; - this.templateLocale = locale; + public HtmlEmailBuilder template(String template) { + this.templateHtml = template; return templateParameter(Map.of()); } @@ -133,6 +132,11 @@ public HtmlEmailBuilder templateParameter(Map parameters) { return this; } + public HtmlEmailBuilder locale(Locale locale) { + this.templateLocale = locale; + return this; + } + public HtmlEmailBuilder attach(File file) { attach(HtmlEmailAttachmentBuilders.forFile(file)); return this; @@ -176,13 +180,16 @@ private void validateConfiguration() throws EmailException { throw new EmailException("Cannot create email without recipients. Please provide at least one valid email address!"); } - if (StringUtils.isBlank(body) && StringUtils.isBlank(templateName)) { + if (StringUtils.isBlank(body) && StringUtils.isBlank(templateHtml)) { throw new EmailException("Cannot create email without content. There must be a configuration for either body or template!"); } - if (StringUtils.isNotBlank(body) && StringUtils.isNotBlank(templateName)) { + if (StringUtils.isNotBlank(body) && StringUtils.isNotBlank(templateHtml)) { throw new EmailException("Cannot create email without ambiguous content. There must be a configuration for either body or template, not both!"); } + if (templateHtml != null && templateLocale == null) { + throw new EmailException("Email creation failed: missing configuration for locale."); + } } private void configureAddresses(HtmlEmail htmlEmail) throws EmailException { @@ -201,11 +208,11 @@ private void configureAddresses(HtmlEmail htmlEmail) throws EmailException { } private void processMessageContent(HtmlEmail htmlEmail, HtmlEmailGenerator htmlEmailGenerator) throws EmailException { - if (templateName != null && templateLocale != null) { + if (templateHtml != null) { templateParameter("subject", subject); templateParameter("recipients", emptyIfNull(toAddresses)); templateParameter("ccRecipients", emptyIfNull(ccAddresses)); - body = htmlEmailGenerator.processTemplate(templateName, contextParameters, templateLocale); + body = htmlEmailGenerator.processTemplate(templateHtml, contextParameters, templateLocale); } htmlEmail.setSubject(subject); diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailGenerator.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailGenerator.java index 99216a5..65f7d76 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailGenerator.java +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/HtmlEmailGenerator.java @@ -88,7 +88,8 @@ default HtmlEmail createHtmlEmailFromTemplate(String subject, Collection templateParameters, Locale locale) throws EmailException { return newHtmlEmail() .subject(subject) - .template(template, locale) + .template(template) + .locale(locale) .templateParameter(templateParameters) .custom(builder -> emptyIfNull(to).forEach(builder::to)) .custom(builder -> emptyIfNull(cc).forEach(builder::cc)) diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/EmailTemplateResolverDao.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/EmailTemplateResolverDao.java new file mode 100644 index 0000000..670045e --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/EmailTemplateResolverDao.java @@ -0,0 +1,22 @@ +package me.cxdev.commerce.toolkit.email.dao; + +import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException; + +import me.cxdev.commerce.toolkit.model.ThymeleafEmailTemplateModel; + +/** + * DAO for retrieving {@link ThymeleafEmailTemplateModel} instances from the database. + * + *

Implementations query the {@code ThymeleafEmailTemplate} type by its unique code + * using FlexibleSearch. + */ +public interface EmailTemplateResolverDao { + /** + * Searches for a {@link ThymeleafEmailTemplateModel} by its unique code. + * + * @param templateName the unique code of the template (e.g. {@code "contact"}, {@code "registration"}) + * @return the matching {@link ThymeleafEmailTemplateModel}, never {@code null} + * @throws UnknownIdentifierException if no template with the given code exists in the database + */ + ThymeleafEmailTemplateModel searchTemplate(String templateName) throws UnknownIdentifierException; +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/impl/DatabaseEmailTemplateResolverDao.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/impl/DatabaseEmailTemplateResolverDao.java new file mode 100644 index 0000000..af2df90 --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/dao/impl/DatabaseEmailTemplateResolverDao.java @@ -0,0 +1,34 @@ +package me.cxdev.commerce.toolkit.email.dao.impl; + +import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException; +import de.hybris.platform.servicelayer.search.FlexibleSearchQuery; +import de.hybris.platform.servicelayer.search.FlexibleSearchService; +import de.hybris.platform.servicelayer.search.SearchResult; + +import org.apache.log4j.Logger; + +import me.cxdev.commerce.toolkit.email.dao.EmailTemplateResolverDao; +import me.cxdev.commerce.toolkit.model.ThymeleafEmailTemplateModel; + +public class DatabaseEmailTemplateResolverDao implements EmailTemplateResolverDao { + + private static final Logger LOG = Logger.getLogger(DatabaseEmailTemplateResolverDao.class); + + private final String QUERY = "SELECT {pk} FROM {ThymeleafEmailTemplate} WHERE {code} = ?templateName"; + private final FlexibleSearchService flexibleSearchService; + + public DatabaseEmailTemplateResolverDao(FlexibleSearchService flexibleSearchService) { + this.flexibleSearchService = flexibleSearchService; + } + + public ThymeleafEmailTemplateModel searchTemplate(String templateName) throws UnknownIdentifierException { + FlexibleSearchQuery query = new FlexibleSearchQuery(QUERY); + query.addQueryParameter("templateName", templateName); + LOG.debug(String.format("Searching for email template with template name: %s", templateName)); + final SearchResult result = flexibleSearchService.search(query); + if (result.getResult().isEmpty()) { + throw new UnknownIdentifierException(String.format("No email template found for template name: %s", templateName)); + } + return result.getResult().getFirst(); + } +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/exception/TemplateNotFoundException.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/exception/TemplateNotFoundException.java new file mode 100644 index 0000000..cd265e7 --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/exception/TemplateNotFoundException.java @@ -0,0 +1,7 @@ +package me.cxdev.commerce.toolkit.email.exception; + +public class TemplateNotFoundException extends RuntimeException { + public TemplateNotFoundException(String message) { + super(message); + } +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/EmailTemplateResolverService.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/EmailTemplateResolverService.java new file mode 100644 index 0000000..6d3f2ec --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/EmailTemplateResolverService.java @@ -0,0 +1,27 @@ +package me.cxdev.commerce.toolkit.email.service; + +import java.util.Locale; + +import me.cxdev.commerce.toolkit.email.exception.TemplateNotFoundException; + +/** + * Resolves localized email templates by name from the database. + * + *

Templates are stored as localized items in the database and can be managed + * via Backoffice or Impex. At runtime this service looks up the matching template + * by name and locale and returns its raw HTML content for further processing by + * the Thymeleaf rendering engine. + */ + +public interface EmailTemplateResolverService { + /** + * Returns the raw HTML content of the named template for the given locale. + * + * @param templateName logical name of the template (e.g. {@code "contact"}, {@code "registration"}) + * @param locale the locale used to select the localized template variant + * @return the resolved HTML template content, never {@code null} + * @throws TemplateNotFoundException if no template with the given name exists for the requested locale + */ + + String resolveEmailTemplate(String templateName, Locale locale) throws TemplateNotFoundException; +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/impl/DatabaseEmailTemplateResolverService.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/impl/DatabaseEmailTemplateResolverService.java new file mode 100644 index 0000000..cfe625c --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/email/service/impl/DatabaseEmailTemplateResolverService.java @@ -0,0 +1,28 @@ +package me.cxdev.commerce.toolkit.email.service.impl; + +import java.util.Locale; + +import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException; + +import me.cxdev.commerce.toolkit.email.dao.EmailTemplateResolverDao; +import me.cxdev.commerce.toolkit.email.exception.TemplateNotFoundException; +import me.cxdev.commerce.toolkit.email.service.EmailTemplateResolverService; +import me.cxdev.commerce.toolkit.model.ThymeleafEmailTemplateModel; + +public class DatabaseEmailTemplateResolverService implements EmailTemplateResolverService { + private final EmailTemplateResolverDao emailTemplateResolverDao; + + public DatabaseEmailTemplateResolverService(EmailTemplateResolverDao emailTemplateResolverDao) { + this.emailTemplateResolverDao = emailTemplateResolverDao; + } + + @Override + public String resolveEmailTemplate(String templateName, Locale locale) throws TemplateNotFoundException { + try { + ThymeleafEmailTemplateModel template = emailTemplateResolverDao.searchTemplate(templateName); + return template.getTemplate(locale); + } catch (UnknownIdentifierException e) { + throw new TemplateNotFoundException(String.format("An error occurred while searching for the email template: %s", e.getMessage())); + } + } +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/impex/ImpexLocalizationHelper.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/impex/ImpexLocalizationHelper.java new file mode 100644 index 0000000..cf43ef0 --- /dev/null +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/src/me/cxdev/commerce/toolkit/impex/ImpexLocalizationHelper.java @@ -0,0 +1,60 @@ +package me.cxdev.commerce.toolkit.impex; + +import java.util.Arrays; +import java.util.HashMap; + +import de.hybris.platform.servicelayer.i18n.util.LanguageUtils; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImpexLocalizationHelper { + private static final Logger LOG = LoggerFactory.getLogger(ImpexLocalizationHelper.class); + + public static boolean languageSupported(final String isocode) { + return LanguageUtils.isLanguagePresent(isocode); + } + + public static boolean languageSupported(final String isocode, String languages) { + if (StringUtils.isBlank(languages)) { + return false; + } + + boolean isContainedInList = Arrays.stream(StringUtils.split(languages, ",")) + .map(StringUtils::trimToEmpty) + .anyMatch(isocode::equalsIgnoreCase); + return isContainedInList && languageSupported(isocode); + } + + public static void filterUnlocalizedLine(HashMap valueLine, Integer column) { + if (isLocalizedLine(valueLine, column)) { + valueLine.clear(); + } + } + + public static void filterUnlocalizedOrPrimaryLine(HashMap valueLine, Integer column, String isocode) { + if (isLocalizedLine(valueLine, column) && !hasCorrectLocale(valueLine, column, isocode)) { + valueLine.clear(); + } + } + + public static void filterLocalizedLine(HashMap valueLine, Integer column, String isocode) { + if (isUnlocalizedLine(valueLine, column) || !hasCorrectLocale(valueLine, column, isocode)) { + valueLine.clear(); + } + } + + public static boolean isLocalizedLine(HashMap valueLine, Integer column) { + return StringUtils.isNotBlank(valueLine.get(column)); + } + + public static boolean isUnlocalizedLine(HashMap valueLine, Integer column) { + return !isLocalizedLine(valueLine, column); + } + + public static boolean hasCorrectLocale(HashMap valueLine, Integer column, String isocode) { + return (StringUtils.isBlank(isocode) && StringUtils.isBlank(valueLine.get(column))) + || StringUtils.equalsIgnoreCase(valueLine.get(column), isocode); + } +} diff --git a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/testsrc/me/cxdev/commerce/toolkit/email/HtmlEmailBuilderTests.java b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/testsrc/me/cxdev/commerce/toolkit/email/HtmlEmailBuilderTests.java index 6b24ea1..9d31abb 100644 --- a/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/testsrc/me/cxdev/commerce/toolkit/email/HtmlEmailBuilderTests.java +++ b/core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/testsrc/me/cxdev/commerce/toolkit/email/HtmlEmailBuilderTests.java @@ -50,7 +50,7 @@ public void failsWithBodyAndTemplate() throws EmailException { builder .to("mail@localhost") .body("body") - .template("template", Locale.ENGLISH) + .template("template") .build(); } @@ -141,7 +141,8 @@ public void testSubjectAndBody() throws Exception { public void testSubjectAndTemplate() throws Exception { HtmlEmail htmlEmail = builder .subject("custom subject for email") - .template("templateName", Locale.ENGLISH) + .template("templateName") + .locale(Locale.ENGLISH) .to("requires-at-least-one-to-address@localhost") .build(); diff --git a/sonar-project.properties b/sonar-project.properties index 635b425..b507d7d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=cxdevtools # This is the name and version displayed in the SonarCloud UI. sonar.projectName=cxdevtools-workspace -sonar.projectVersion=5.0.1 +sonar.projectVersion=5.0.2 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=core-customize/hybris/bin/custom/cxdevtools