Skip to content
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
40 changes: 37 additions & 3 deletions core-customize/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,47 @@ if (project.hasProperty("CXDEV_ARTEFACT_BASEURL") && project.hasProperty("CXDEV_

val COMMERCE_VERSION = CCV2.manifest.effectiveVersion
tasks.register<Download>("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") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated" classprefix="CxBackoffice" jaloLogicFree="true" name="cxdevbackoffice" version="5.0.1" usemaven="true">
<extension abstractclassprefix="Generated" classprefix="CxBackoffice" jaloLogicFree="true" name="cxdevbackoffice" version="5.0.2" usemaven="true">
<requires-extension name="cxdevtoolkit"/>
<requires-extension name="backoffice"/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.cxdev</groupId>
<artifactId>cxdevbackoffice</artifactId>
<version>5.0.1</version>
<version>5.0.2</version>
<packaging>jar</packaging>
<dependencies>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated" classprefix="CxEnvConfig" jaloLogicFree="true"
name="cxdevenvconfig" version="5.0.1" usemaven="true">
name="cxdevenvconfig" version="5.0.2" usemaven="true">
<requires-extension name="commercewebservices"/>
<coremodule generated="true" manager="de.hybris.platform.jalo.extension.GenericManager" packageroot="me.cxdev.commerce.config"/>
</extension>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.cxdev</groupId>
<artifactId>cxdevenvconfig</artifactId>
<version>5.0.1</version>
<version>5.0.2</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated" classprefix="CxDevProxy" jaloLogicFree="true" name="cxdevproxy" version="5.0.1" usemaven="true">
<extension abstractclassprefix="Generated" classprefix="CxDevProxy" jaloLogicFree="true" name="cxdevproxy" version="5.0.2" usemaven="true">
<requires-extension name="oauth2commons"/>
<coremodule generated="true" manager="de.hybris.platform.jalo.extension.GenericManager" packageroot="me.cxdev.commerce.proxy"/>
</extension>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.cxdev</groupId>
<artifactId>cxdevproxy</artifactId>
<version>5.0.1</version>
<version>5.0.2</version>
<packaging>jar</packaging>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<property name="enabled" value="${cxdevproxy.enabled}" />

<property name="serverBindAddress" value="${cxdevproxy.server.bindaddress}" />
<property name="serverProtocol" value="${cxdevproxy.server.protocol}" />
<property name="serverPort" value="${cxdevproxy.server.port}" />

<property name="sslEnabled" value="${cxdevproxy.ssl.enabled}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- Forwarded-For Header Interceptor -->
<bean id="cxdevproxyInterceptorForwardedHeaders" class="me.cxdev.commerce.proxy.interceptor.ForwardedHeadersInterceptor">
<property name="serverHostname" value="${cxdevproxy.server.hostname}" />
<property name="serverProtocol" value="${cxdevproxy.server.protocol}" />
<property name="serverPort" value="${cxdevproxy.server.port}" />
</bean>

<!-- Prepared CORS Injector Interceptor, needs to be activated if needed -->
<bean id="cxdevproxyInterceptorCorsInjector" class="me.cxdev.commerce.proxy.interceptor.CorsInjectorInterceptor"/>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
return [
interceptor()
.constrainedBy( isMethod("GET"), pathMatches("/**/carts/current") )
.perform( jsonResponse('{"type": "cartWsDTO", "totalItems": 5}') )
]
return []
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
def interceptor = []
interceptor << forwardedHeaders
return interceptor
return []
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<String> activeBackendContexts = determineBackendContexts();
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ void setUp() {

// Setup Spring ApplicationContext mocks with custom bean names to test the prefix stripping
Map<String, ProxyExchangeInterceptor> interceptors = new HashMap<>();
interceptors.put("cxdevproxyInterceptorForwardedHeaders", mockInterceptor);
interceptors.put("customInterceptorWithoutPrefix", mockInterceptor); // Edge case

Map<String, ProxyExchangeInterceptorCondition> conditions = new HashMap<>();
Expand Down Expand Up @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated" classprefix="CxReporting" jaloLogicFree="true" name="cxdevreporting" version="5.0.1" usemaven="true">
<extension abstractclassprefix="Generated" classprefix="CxReporting" jaloLogicFree="true" name="cxdevreporting" version="5.0.2" usemaven="true">
<requires-extension name="platformbackoffice"/>
<requires-extension name="commercefacades"/>
<requires-extension name="cxdevtoolkit"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.cxdev</groupId>
<artifactId>cxdevreporting</artifactId>
<version>5.0.1</version>
<version>5.0.2</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
Expand Down
53 changes: 43 additions & 10 deletions core-customize/hybris/bin/custom/cxdevtools/cxdevtoolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ; "<!DOCTYPE html>
<html>
<body>
<p th:text=""'Name: ' + $d{firstName} + ' ' + $d{lastName}""></p>
<p th:text=""'E-Mail: ' + $d{email}""></p>
</body>
</html>" ;
```

**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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated" classprefix="Toolkit" jaloLogicFree="true"
name="cxdevtoolkit" version="5.0.1" usemaven="true">
name="cxdevtoolkit" version="5.0.2" usemaven="true">
<requires-extension name="backoffice"/>
<coremodule generated="true" manager="de.hybris.platform.jalo.extension.GenericManager" packageroot="me.cxdev.commerce.toolkit"/>
<meta key="backoffice-module" value="true"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.cxdev</groupId>
<artifactId>cxdevtoolkit</artifactId>
<version>5.0.1</version>
<version>5.0.2</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
Expand Down
Loading
Loading