Salesforce:通过 Java 上传 Apex 触发器

问题描述 投票:0回答:1

我想通过 Java 将 ApexTrigger 上传到 Salesforce,因此据我所知,我的可用选项是:

  • 通过 REST 上传
  • 通过 SOAP 上传
  • 通过预定义/创建的 Maven 工件库上传

对于身份验证,我可以选择使用登录到 REST API 时从 Salesforce 返回的 OAuth 令牌用户名、密码、安全令牌、客户端 ID、客户端密钥,而第一种方法是重用REST API 令牌将是我最喜欢的一个。

我的实际尝试还没有成功,我的问题显然是如何让它发挥作用或者我的错误在哪里。

我尝试过以下方法:

  • 通过 元数据 API 上传
    createMetadata()
    • developer.salesforce.com:元数据 API 开发人员指南 |基于CRUD的元数据开发
  • 通过工具 API 上传
    POST /sobjects/SObjectName/
    • developer.salesforce.com 工具 API |休息资源

元数据API方法

我从 Metadata API 方法开始:

@Test
    void testTriggerUploadMetadataApi() {
        String username = "REDACTED";
        String password = "REDACTED";
        String securityToken = "REDACTED";
        String url = "https://REDACTEDDOMAIN.my.salesforce.com/services/Soap";
        String authPrefix = "/u";
        String servicePrefix = "/m";
        String version = "/60.0";


        LoginResponse loginResponse = this.authController
                .authLoginPost(new Login("REDACTED", "REDACTED").thirdPartyConfig(thirdPartyConfig))
                .block();
        assertNotNull(loginResponse);
        // Session is here a wrapper for the actual REST API OAuth Token with additional metadata
        Session session =
                assertDoesNotThrow(() -> new ObjectMapper().readValue(loginResponse.getToken(), Session.class));

        ConnectorConfig connectorConfig = new ConnectorConfig();
        connectorConfig.setUsername(username);
        connectorConfig.setPassword(password + securityToken);
        connectorConfig.setAuthEndpoint("https://login.salesforce.com/service/Soap" + authPrefix + version);
        connectorConfig.setServiceEndpoint(url + servicePrefix + version);
        connectorConfig.setProxy("REDACTED", 8080);
        Transport transport = new JdkHttpTransport(connectorConfig);
        connectorConfig.setTransport(transport.getClass());

        MetadataConnection metadataConnection = assertDoesNotThrow(() -> new MetadataConnection(connectorConfig));
        metadataConnection.setSessionHeader(session.getBearerTokenString());

        ApexTrigger apexTrigger = new ApexTrigger();
        apexTrigger.setFullName("SomeTrigger");
        apexTrigger.setStatus(ApexCodeUnitStatus.Active);
        apexTrigger.setApiVersion(56.0);
        apexTrigger.setContent(
                "trigger SomeTrigger on Account(after insert, after update {}".getBytes(StandardCharsets.UTF_8));

        assertDoesNotThrow(() -> metadataConnection.upsertMetadata(new Metadata[] {apexTrigger}));
    }

结果是:

org.opentest4j.AssertionFailedError: Unexpected exception thrown: com.sforce.ws.SoapFaultException: INVALID_TYPE: This type of object is not available for this organization
    at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
    at org.junit.jupiter.api.AssertDoesNotThrow.createAssertionFailedError(AssertDoesNotThrow.java:84)
    at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:75)
    at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:58)
    at org.junit.jupiter.api.Assertions.assertDoesNotThrow(Assertions.java:3228)
    at redacted.package.name.SFLoginTest.testTriggerUploadMetadataApi(SFLoginTest.java:139)

然后我开始进行一些研究,发现以下开拓者线程在评论中来自Salesforce的Daniel Ballinger指出这可能是不可能的,因为“create()、delete()不支持此元数据类型,和 update() 调用。 虽然不明确,但这也适用于 createMetadata() 方法,create() 已被简化为该方法。”

他还指出“相反,您可以使用元数据API部署方法或工具API来创建Apex类。”以便我进一步研究在哪里找到这个trailblazer线程,其中Sunad Rasane指出他使用了Tooling API而是指向此stackexchange线程的链接,其中已经存在通过Tooling API触发上传的示例,因此我选择使用Tooling API,因为它看起来使用起来很方便:

工具 API 方法

@Test
    void testTriggerUploadToolingApi() {

        Map<String, Object> thirdPartyConfig = getThirdPartyConfig();

        LoginResponse loginResponse = this.authController
                .authLoginPost(new Login("REDACTED", "REDACTED").thirdPartyConfig(thirdPartyConfig))
                .block();
        assertNotNull(loginResponse);
        // Session is here a wrapper for the actual REST API OAuth Token with additional metadata
        Session session =
                assertDoesNotThrow(() -> new ObjectMapper().readValue(loginResponse.getToken(), Session.class));
        String url = session.getBaseUrl() + "/services/data/" + session.getApiVersion() + "/sobjects/ApexTrigger";
        System.out.println("Request URL: [" + url + "]");

        Map<String, String> apexTrigger = new LinkedHashMap<>();
        apexTrigger.put("Name", "UploadTest_AccountTrigger");
        apexTrigger.put("TableEnumOrId", "Account");
        apexTrigger.put(
                "Body",
                assertDoesNotThrow(() -> Files.readString(
                        Path.of("src/test/resources/salesforce/apex/SimpleAccountTriggerExpected.txt"))));

        String requestBody = assertDoesNotThrow(() -> new ObjectMapper().writeValueAsString(apexTrigger));
        System.out.println("Request body: [" + requestBody + "]");

        HttpClient httpClient = HttpClient.newBuilder()
                .proxy(ProxySelector.of(new InetSocketAddress("REDACTED", 8080)))
                .connectTimeout(Duration.of(60, ChronoUnit.SECONDS))
                .build();
        HttpRequest uploadRequest = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.of(60, ChronoUnit.SECONDS))
                .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
                .header("Authorization", "OAuth " + session.getBearerTokenString())
                .header("Content-Type", "application/json")
                .build();
        HttpResponse<String> response =
                assertDoesNotThrow(() -> httpClient.send(uploadRequest, HttpResponse.BodyHandlers.ofString()));
        System.out.println("Status Code: [" + response.statusCode()+ "]");
        System.out.println("Body: [" + response.body() + "]");
        
        assertTrue(String.valueOf(response.statusCode()).startsWith("2"));
    }

结果是:

Expected :true
Actual   :false
<Click to see difference>

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
    at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
    at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
    at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
    at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
    at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:31)
    at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:183)
    at redacted.package.nameSFLoginTest.testTriggerUploadToolingApi(SFLoginTest.java:83)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.micronaut.test.extensions.junit5.MicronautJunit5Extension$2.proceed(MicronautJunit5Extension.java:142)
    at io.micronaut.test.extensions.AbstractMicronautExtension.interceptEach(AbstractMicronautExtension.java:157)
    at io.micronaut.test.extensions.AbstractMicronautExtension.interceptTest(AbstractMicronautExtension.java:114)
    at io.micronaut.test.extensions.junit5.MicronautJunit5Extension.interceptTestMethod(MicronautJunit5Extension.java:129)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

STDOUT 上有以下输出:

Request URL: [https://REDACTEDDOMAIN.my.salesforce.com/services/data/56.0/sobjects/ApexTrigger]
Request body: [{"Name":"UploadTest_AccountTrigger","TableEnumOrId":"Account","Body":"trigger Account_AccountWebhook on Account (after insert, after update) {}"}]
Status Code: [404]
Body: [[{"errorCode":"NOT_FOUND","message":"The requested resource does not exist"}]]
java salesforce apex sfdc
1个回答
0
投票

因此,在令人沮丧地发现该版本缺少“v”前缀之后,Tooling API 接近基本 URL,现在它按照描述的方式工作。

您可以在下面找到构建请求 URL 的正确方法(将其替换为 Tooling API Approach:

String url = session.getBaseUrl() + "/services/data/v" + session.getApiVersion() + "/sobjects/ApexTrigger";
© www.soinside.com 2019 - 2024. All rights reserved.