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.

소스 정보

저장소
SAP-samples/btp-neo-java-app-migration
최근 소스 활동
2026년 7월 29일 13:26
감지된 SKILL.md 언어
영어
스타
6
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
7 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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) |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기