Skip to main content

mail-destinations

Invoke this skill to configure mail sessions via Destination service. Detects javax.mail.Session or jakarta.mail.Session resource-ref in web.xml, or mail session JNDI lookups. Replaces Neo JNDI mail with manual session creation.

Quellinformationen

Repository
SAP-samples/btp-neo-java-app-migration
Letzte Quellaktivität
29. Juli 2026 um 13:26
Erkannte Sprache von SKILL.md
Englisch
Sterne
6
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
7 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
mail-destinations
description
Invoke this skill to configure mail sessions via Destination service. Detects javax.mail.Session or jakarta.mail.Session resource-ref in web.xml, or mail session JNDI lookups. Replaces Neo JNDI mail with manual session creation.
disable-model-invocation
false
allowed-tools
Read, Edit, Write, Bash, Grep, Glob
# Mail Session Configure mail sessions via the Destination service. ## Purpose Replace Neo's `javax.mail.Session` JNDI resource with manual session creation using mail server configuration from the Destination service. ## Detection This skill applies if any of these patterns are found: ### In web.xml ```xml <resource-ref> <res-ref-name>mail/Session</res-ref-name> <res-type>javax.mail.Session</res-type> </resource-ref> ``` ### In Java source files ```java import javax.mail.Session; import javax.annotation.Resource; @Resource(name = "mail/Session") private Session mailSession; // OR JNDI lookup Session session = (Session) ctx.lookup("java:comp/env/mail/Session"); ``` ## Prerequisites > **Working directory:** This skill must run inside the `-cf-migration` copy of your app, created by `jakarta-java25-migration` or `neo-to-cf-migration-orchestrator`. If your current directory does not end in `-cf-migration`, switch to it before proceeding. Before invoking this skill, ensure you have invoked: 1. **sdk-replacement** - `Use the sdk-replacement skill` - Sets up SAP Cloud SDK - REQUIRED before this skill 2. **destinations** - `Use the destinations skill` - Configures Destination service - REQUIRED before this skill 3. **connectivity-onpremise** - `Use the connectivity-onpremise skill` - Required for on-premise mail servers (if applicable) ## Transformation Steps ### Step 1: Remove Resource Reference from web.xml **Remove this from web.xml:** ```xml <resource-ref> <res-ref-name>mail/Session</res-ref-name> <res-type>javax.mail.Session</res-type> </resource-ref> ``` ### Step 2: Add Mail Dependencies Add to `pom.xml`: > 🛑 **HARD RULE — ship a Jakarta Mail *implementation*, not just the API.** > On Jakarta EE 10 / Java 25 (`sap_java_buildpack_jakarta`) the Jakarta Mail > **API** (`jakarta.mail:jakarta.mail-api`, often pulled transitively by an EE 10 > BOM) contains only interfaces. Without a runtime **implementation** on the > classpath, the app builds and deploys fine but throws at the first send: > `java.lang.IllegalStateException: No provider of jakarta.mail.util.StreamProvider > was found` (HTTP 500). The implementation is **Angus Mail** > (`org.eclipse.angus:jakarta.mail`) — it registers the provider via > `META-INF/services`. Always declare the implementation explicitly (it brings > the API transitively), so the provider is packaged into the WAR: ```xml <properties> <!-- Angus Mail is the Jakarta Mail 2.1 (EE 10) reference implementation. It transitively provides the jakarta.mail-api and the StreamProvider. --> <angus-mail-version>2.0.3</angus-mail-version> <jakarta-activation-version>2.1.3</jakarta-activation-version> </properties> <dependencies> <!-- Jakarta Mail IMPLEMENTATION (Angus Mail) — provides the API + the runtime StreamProvider. Do NOT depend on jakarta.mail-api alone. --> <dependency> <groupId>org.eclipse.angus</groupId> <artifactId>jakarta.mail</artifactId> <version>${angus-mail-version}</version> </dependency> <!-- Jakarta Activation implementation (required for mail) --> <dependency> <groupId>org.eclipse.angus</groupId> <artifactId>angus-activation</artifactId> <version>${jakarta-activation-version}</version> </dependency> </dependencies> ``` > > ✅ **After building the WAR, verify the impl is packaged:** > `jar tf target/*.war | grep -i 'angus-mail\|jakarta.mail'` must list the Angus > Mail jar under `WEB-INF/lib/`. If only `jakarta.mail-api-*.jar` is present, the > app will 500 at runtime with the StreamProvider error above. ### Step 3: Copy Mail Session Helper Classes Copy the mail session helper classes from [assets/session/](assets/session/) to your project's source directory. These classes handle mail session creation from destination configuration. **Required files:** - `MailSession.java` - Main session factory - `MailAuthenticator.java` - Password authentication - `MailPropertiesHandler.java` - Extracts mail properties from destination - `OnPremiseSMTPProvider.java` - Custom SMTP provider for on-premise - `OnPremiseSMTPTransport.java` - SMTP transport via Cloud Connector - `ConnectivitySocks5ProxySocket.java` - SOCKS5 proxy for on-premise connectivity Copy these to `src/main/java/com/example/document/` (the assets all declare `package com.example.document;`). > **Note on the package**: `com.example.document` is a placeholder. Rename to your project's package > (e.g. `com.acme.mail`) when you copy the assets in, and update the imports below to match. **Alternative: Simple MailSessionFactory** For simpler scenarios (internet mail only), you can create a minimal `MailSessionFactory.java`: ```java package com.example.document; import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; import jakarta.mail.Authenticator; import jakarta.mail.PasswordAuthentication; import jakarta.mail.Session; import java.util.Optional; import java.util.Properties; public class MailSessionFactory { /** * Create mail session from destination configuration */ public static Session createSession(String destinationName) { Destination destination = DestinationAccessor.getDestination(destinationName); // Get mail server properties from destination String host = getProperty(destination, "mail.smtp.host") .orElseGet(() -> extractHost(destination)); String port = getProperty(destination, "mail.smtp.port") .orElse("587"); String user = getProperty(destination, "mail.user") .orElse(getProperty(destination, "User").orElse(null)); String password = getProperty(destination, "mail.password") .orElse(getProperty(destination, "Password").orElse(null)); boolean auth = getProperty(destination, "mail.smtp.auth") .map(Boolean::parseBoolean) .orElse(user != null); boolean starttls = getProperty(destination, "mail.smtp.starttls.enable") .map(Boolean::parseBoolean) .orElse(true); Properties props = new Properties(); props.put("mail.smtp.host", host); props.put("mail.smtp.port", port); props.put("mail.smtp.auth", String.valueOf(auth)); props.put("mail.smtp.starttls.enable", String.valueOf(starttls)); // Store user/password in session properties for transport.connect() usage if (user != null) props.put("mail.smtp.user", user); if (password != null) props.put("mail.smtp.password", password); // Add any additional properties from destination addOptionalProperty(props, destination, "mail.smtp.ssl.enable"); addOptionalProperty(props, destination, "mail.smtp.ssl.trust"); addOptionalProperty(props, destination, "mail.smtp.ssl.checkserveridentity"); addOptionalProperty(props, destination, "mail.smtp.connectiontimeout"); addOptionalProperty(props, destination, "mail.smtp.timeout"); addOptionalProperty(props, destination, "mail.smtp.from"); addOptionalProperty(props, destination, "mail.from"); if (auth && user != null && password != null) { final String finalUser = user; final String finalPassword = password; return Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(finalUser, finalPassword); } }); } else { return Session.getInstance(props); } } private static String extractHost(Destination destination) { // Try to extract host from URL property return getProperty(destination, "URL") .map(url -> { try { return new java.net.URL(url).getHost(); } catch (Exception e) { return url; // Return as-is if not a valid URL } }) .orElseThrow(() -> new RuntimeException("Mail host not configured")); } private static Optional<String> getProperty(Destination destination, String key) { return destination.get(key).toJavaOptional() .map(Object::toString); } private static void addOptionalProperty(Properties props, Destination destination, String key) { getProperty(destination, key).ifPresent(value -> props.put(key, value)); } } ``` ### Step 4: Update Mail Sending Code **Before (Neo):** ```java import javax.annotation.Resource; import javax.mail.*; import javax.mail.internet.*; import javax.servlet.http.*; public class MailServlet extends HttpServlet { @Resource(name = "mail/Session") private Session mailSession; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String to = request.getParameter("to"); String subject = request.getParameter("subject"); String body = request.getParameter("body"); try { Message message = new MimeMessage(mailSession); message.setFrom(new InternetAddress("sender@example.com")); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject); message.setText(body); Transport.send(message); response.getWriter().println("Email sent successfully"); } catch (MessagingException e) { throw new ServletException("Failed to send email", e); } } } ``` **After (Cloud Foundry):** ```java import com.example.document.MailSessionFactory; import jakarta.mail.*; import jakarta.mail.internet.*; import jakarta.servlet.http.*; public class MailServlet extends HttpServlet { private static final String MAIL_DESTINATION = "mail-destination"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String to = request.getParameter("to"); String subject = request.getParameter("subject"); String body = request.getParameter("body"); try { // Create session from destination Session mailSession = MailSessionFactory.createSession(MAIL_DESTINATION); Message message = new MimeMessage(mailSession); // Use mail.smtp.from from destination instead of hardcoded address String from = mailSession.getProperty("mail.smtp.from"); if (from == null) from = mailSession.getProperty("mail.from"); message.setFrom(new InternetAddress(from != null ? from : "sender@example.com")); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject); message.setText(body); // Important: use Transport instance with explicit credentials. // Transport.send(message) does NOT pass credentials from session properties. Transport transport = mailSession.getTransport(); String user = mailSession.getProperty("mail.smtp.user"); String password = mailSession.getProperty("mail.smtp.password"); try { if (user != null) { transport.connect( mailSession.getProperty("mail.smtp.host"), Integer.parseInt(mailSession.getProperty("mail.smtp.port")), user, password); } else { transport.connect(); } transport.sendMessage(message, message.getAllRecipients()); } finally { transport.close(); } response.getWriter().println("Email sent successfully"); } catch (MessagingException e) { throw new ServletException("Failed to send email", e); } } } ``` > **Important:** Do not use `Transport.send(message)` — it creates a new transport internally and does NOT pass user/password from session properties. Always use `transport.connect(host, port, user, password)` explicitly. ### Step 5: Create Mail Destination #### Option A: Internet Mail Server Create destination in BTP Cockpit: | Property | Value | |----------|-------| | Name | `mail-destination` | | Type | HTTP | | URL | `smtp://smtp.example.com` | | Proxy Type | Internet | | Authentication | BasicAuthentication | | User | `your-email@example.com` | | Password | `your-password` | **Additional Properties:** | Property | Value | |----------|-------| | `mail.smtp.host` | `smtp.example.com` | | `mail.smtp.port` | `587` | | `mail.smtp.auth` | `true` | | `mail.smtp.starttls.enable` | `true` | | `mail.smtp.from` | `sender@example.com` | | `mail.smtp.ssl.checkserveridentity` | `true` | > **Important:** Use `mail.smtp.from` to set the sender address. Do NOT hardcode sender addresses like `noreply@mail.hana.ondemand.com` in your code — these Neo-era addresses won't work on CF. The `mail.smtp.from` address must be verified with your SMTP provider. #### Option B: On-Premise Mail Server For on-premise mail servers via Cloud Connector: | Property | Value | |----------|-------| | Name | `mail-destination` | | Type | HTTP | | URL | `smtp://mail-virtual-host:25` | | Proxy Type | **OnPremise** | | Authentication | NoAuthentication (or as needed) |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen