Skip to content

Commit

Permalink
Merge branch '1.5-next' into feat/mailgun
Browse files Browse the repository at this point in the history
  • Loading branch information
JPPortier committed Jan 29, 2025
2 parents 2918854 + 59c040f commit 28b8db8
Show file tree
Hide file tree
Showing 315 changed files with 30,100 additions and 732 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
* @Dovchik @650elx @krogers0607 @asein-sinch @JPPortier
* @Dovchik @krogers0607 @asein-sinch @JPPortier
112 changes: 87 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ For more information on the Sinch APIs on which this SDK is based, refer to the
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Getting started](#getting-started)
- [Logging]()
- [Supported APIs](#supported-apis)
- [Logging](#logging)
- [Onboarding](#onboarding)
- [3rd party dependencies](#3rd-party-dependencies)

## Prerequisites

Expand Down Expand Up @@ -40,7 +43,9 @@ Once the SDK is installed, you must start by initializing the main client class.

### Client initialization

To initialize communication with the Sinch servers, credentials obtained from the Sinch dashboard must be provided to the main client class of this SDK. It's highly recommended to not hardcode these credentials and to load them from environment variables instead.
To initialize communication with the Sinch servers, credentials obtained from the Sinch dashboard must be provided to the main client class of this SDK.

It's highly recommended to not hardcode these credentials and to load them from environment variables instead.

Sample:

Expand All @@ -49,49 +54,106 @@ import com.sinch.sdk.SinchClient;
import com.sinch.sdk.models.Configuration;

...
Configuration configuration = Configuration.builder().setKeyId(PARAM_KEY_ID)
.setKeySecret(PARAM_KEY_SECRET)
.setProjectId(PARAM_PROJECT_ID)
.build();
Configuration configuration = Configuration.builder()
.setKeyId(PARAM_KEY_ID)
.setKeySecret(PARAM_KEY_SECRET)
.setProjectId(PARAM_PROJECT_ID)
.build();
...
SinchClient client = new SinchClient(configuration);
```

## Products
## Supported APIs

Here is the list of the Sinch products and their level of support by the Java SDK:

| API Category | API Name | Status |
|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|:------:|
| Conversation | Conversation API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/conversation/package-summary.html) ||
| Messaging | SMS API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/sms/package-summary.html) ||
| Numbers & Connectivity | Numbers API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/numbers/package-summary.html) ||
| Verification | Verification API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/verification/package-summary.html) ||
| Voice and Video | Voice API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/voice/package-summary.html) ||
| API Category | API Name | Status |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------|:------:|
| Conversation | Conversation API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/conversation/package-summary.html) | |
| Messaging | SMS API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/sms/package-summary.html) | |
| Numbers & Connectivity | Numbers API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/numbers/package-summary.html) | |
| Verification | Verification API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/verification/package-summary.html) | |
| Voice and Video | Voice API [(javadoc)](https://developers.sinch.com/java-sdk/apidocs/com/sinch/sdk/domains/voice/package-summary.html) | |


## Logging

The SDK uses the Java 8 logging feature ([java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html))
Loggers and severity can be configured by using a `logging.properties` file like (see sample-app/src/main/resources/:
The SDK uses the Java 8 logging feature ([java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html#package.description))

When using java logging, Loggers and severity can be configured by using a `logging.properties` file like:
```
handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = INFO
com.sinch.sdk.level = INFO
# java.util.logging configuration sample
# See https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html#package.description
#
# Layers logging severity configuration
#
# Sinch SDK
com.sinch.level = INFO
# Apache HTTP Client
# org.apache.hc.level =
#
# console output handler sample
#
handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s %2$s] %5$s %n
java.util.logging.ConsoleHandler.level = FINEST
```
If you are using a different logging framework (such as SLF4J or Spring), please refer to the documentation for your specific framework.

## Onboarding

To improve onboarding experience, following resources are available:
- Sinch developpers online doucmentation: https://developers.sinch.com
- A dedicated Java SDK quickstart repository with tutorials and templates enabling to start quickly a new project: https://github.com/sinch/sinch-sdk-java-quickstart
- A dedicated Java SDK sippets repository with basic code snippets: https://github.com/sinch/sinch-sdk-java-snippets
- Java SDK online javdoc: https://developers.sinch.com/java-sdk/apidocs
To enhance the onboarding experience, the following resources are available:
- Sinch Online Developers Documentation: https://developers.sinch.com
- Java SDK Online Javadoc: https://developers.sinch.com/java-sdk/apidocs
- Java SDK Quickstart Repository: A repository with tutorials and templates to help you quickly start a new project: https://github.com/sinch/sinch-sdk-java-quickstart
- Java SDK Snippets Repository: A collection of basic code snippets for reference: https://github.com/sinch/sinch-sdk-java-snippets

## 3rd party dependencies
The SDK relies on the following third-party dependencies:
- [Jackson's library](https://github.com/FasterXML/jackson-jakarta-rs-providers): Provides JSON serialization/deserialization functionality.
- [Apache HTTP client](https://hc.apache.org/httpcomponents-client-5.4.x/5.4.1/httpclient5/project-info.html): Manages communication with Sinch products' REST APIs

### Jackson/Java EE/jaxrs/Jakarta Compatibility Consideration
The transition from <code>javax</code> to <code>jakarta</code> namespaces with the Java EE to Jakarta EE migration may cause compatibility issues. Refer to [Oracle's note about the transition](https://blogs.oracle.com/javamagazine/post/transition-from-java-ee-to-jakarta-ee) for additional details.

Jackson maintainers provide two variants of the library:
> (*) NOTE: Jakarta-RS is the package under jakarta.ws.rs, replacing older JAX-RS which lived under javax.ws.rs. For JAX-RS variant, see repo jackson-jaxrs-providers
By default, the Sinch Java SDK uses the "new" [jackson-jakarta-rs-providers](https://github.com/FasterXML/jackson-jakarta-rs-providers). However, you can switch to the "older" [jackson-jaxrs-providers](https://github.com/FasterXML/jackson-jaxrs-providers) if required.

#### Switching to <code>jackson-jaxrs-providers</code>
Depending on your use case, you may need to adjust dependencies to enable <code>jaxrs</code> usage.

Add the following dependency to your configuration:
Note: Replace <code>VERSION-YOU-WANT-TO-BE-USED</code> by a Jackson version of at least `2.15.2`.
```xml
...
<dependency>
<groupId>com.sinch.sdk</groupId>
<artifactId>sinch-sdk-java</artifactId>
<version>${sinch.sdk.java.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jakarta-rs-json-provider</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>VERSION-YOU-WANT-TO-BE-USED</version>
</dependency>
...
```
## License

This project is licensed under the Apache License. See the [LICENSE](LICENSE) file for the license text.
This project is licensed under the Apache License.

See the [LICENSE](LICENSE) file for the license text.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sinch.sdk.domains.conversation.api.v1.adapters;
package com.sinch.sdk.auth;

import com.sinch.sdk.core.exceptions.ApiAuthException;
import com.sinch.sdk.core.utils.MapUtils;
Expand All @@ -12,15 +12,15 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class ConversationWebhooksAuthenticationValidation {
public class HmacAuthenticationValidation {

private static final Logger LOGGER =
Logger.getLogger(ConversationWebhooksAuthenticationValidation.class.getName());
Logger.getLogger(HmacAuthenticationValidation.class.getName());

static final String TIMESTAMP_HEADER = "x-sinch-webhook-signature-timestamp";
static final String NONCE_HEADER = "x-sinch-webhook-signature-nonce";
static final String ALGORITHM_HEADER = "x-sinch-webhook-signature-algorithm";
static final String SIGNATURE_HEADER = "x-sinch-webhook-signature";
public static final String TIMESTAMP_HEADER = "x-sinch-webhook-signature-timestamp";
public static final String NONCE_HEADER = "x-sinch-webhook-signature-nonce";
public static final String ALGORITHM_HEADER = "x-sinch-webhook-signature-algorithm";
public static final String SIGNATURE_HEADER = "x-sinch-webhook-signature";

public boolean validateAuthenticationHeader(
String secret, Map<String, String> _headers, String jsonPayload) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sinch.sdk.domains.conversation.api.v1.adapters;

import com.sinch.sdk.auth.HmacAuthenticationValidation;
import com.sinch.sdk.auth.adapters.OAuthManager;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
Expand Down Expand Up @@ -118,11 +119,7 @@ public WebHooksService webhooks() {
if (null == this.webhooks) {
this.webhooks =
new WebHooksService(
uriUUID,
context,
httpClient,
authManagers,
new ConversationWebhooksAuthenticationValidation());
uriUUID, context, httpClient, authManagers, new HmacAuthenticationValidation());
}
return this.webhooks;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sinch.sdk.domains.conversation.api.v1.adapters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sinch.sdk.auth.HmacAuthenticationValidation;
import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
Expand All @@ -22,7 +23,7 @@

public class WebHooksService implements com.sinch.sdk.domains.conversation.api.v1.WebHooksService {

private final ConversationWebhooksAuthenticationValidation authenticationChecker;
private final HmacAuthenticationValidation authenticationChecker;

private final String uriUUID;
private final WebhooksApi api;
Expand All @@ -32,7 +33,7 @@ public WebHooksService(
ConversationContext context,
HttpClient httpClient,
Map<String, AuthManager> authManagers,
ConversationWebhooksAuthenticationValidation authenticationChecker) {
HmacAuthenticationValidation authenticationChecker) {
this.authenticationChecker = authenticationChecker;
this.uriUUID = uriUUID;
this.api = new WebhooksApi(httpClient, context.getServer(), authManagers, new HttpMapper());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.sinch.sdk.core.models.OptionalValue;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.conversation.models.v1.ConversationChannel;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.card.CardMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.card.CardMessageImpl;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.carousel.CarouselMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.carousel.CarouselMessageMapper;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.channelspecific.ChannelSpecificMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.choice.ChoiceMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.choice.ChoiceMessageImpl;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.contactinfo.ContactInfoMessage;
Expand All @@ -22,6 +24,7 @@
import com.sinch.sdk.domains.conversation.models.v1.messages.types.template.TemplateMessageMapper;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.text.TextMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.text.TextMessageImpl;
import java.util.Map;

public class AppMessageInternalMapper {

Expand Down Expand Up @@ -87,6 +90,14 @@ public OptionalValue<TemplateMessage> templateMessage() {
public OptionalValue<TextMessage> textMessage() {
return super.textMessage();
}

@Override
@JsonSerialize(
using = ChannelSpecificMessageInternalMapper.ChannelSpecificMessageMapSerializer.class)
public OptionalValue<Map<ConversationChannel, ChannelSpecificMessage>>
channelSpecificMessage() {
return super.channelSpecificMessage();
}
}

static class AppMessageInternalBuilderMapperMixin extends AppMessageInternalImpl.Builder {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
package com.sinch.sdk.domains.conversation.models.v1.messages.internal;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sinch.sdk.core.models.OptionalValue;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.conversation.models.v1.ConversationChannel;
import com.sinch.sdk.domains.conversation.models.v1.messages.internal.ChannelSpecificMessageInternal.MessageTypeEnum;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.channelspecific.ChannelSpecificMessage;
import com.sinch.sdk.domains.conversation.models.v1.messages.types.channelspecific.whatsapp.flows.FlowChannelSpecificMessage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ChannelSpecificMessageInternalMapper {
private static final Logger LOGGER =
Logger.getLogger(ChannelSpecificMessageInternalMapper.class.getName());

public static void initMapper() {
SimpleModule module =
new SimpleModule().addDeserializer(ChannelSpecificMessage.class, new Deserializer());

Mapper.getInstance().registerModule(module);
}

Expand All @@ -30,10 +44,80 @@ public Deserializer(Class<ChannelSpecificMessageInternal> vc) {
public ChannelSpecificMessage deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {

ChannelSpecificMessageMessageInternalImpl deserialized =
Object deserialized = jp.readValueAs(ChannelSpecificMessageInternal.class);

if (null == deserialized) {
return null;
}
if (!(deserialized instanceof ChannelSpecificMessageInternal)) {
// do not throw exception to not break all schema's deserialization
LOGGER.log(Level.SEVERE, "Input data does not match schema: {}", deserialized);
return null;
}

ChannelSpecificMessageInternalImpl channelSpecificMessageInternalImpl =
(ChannelSpecificMessageInternalImpl) deserialized;
ChannelSpecificMessageMessageInternalImpl channelSpecificMessageMessageInternal =
(ChannelSpecificMessageMessageInternalImpl)
jp.readValueAs(ChannelSpecificMessageMessageInternal.class);
return (ChannelSpecificMessage) deserialized.getActualInstance();
channelSpecificMessageInternalImpl.getMessage();

if (null == channelSpecificMessageMessageInternal) {
return null;
}
return (ChannelSpecificMessage) channelSpecificMessageMessageInternal.getActualInstance();
}
}

static class ChannelSpecificMessageMapSerializer
extends StdSerializer<OptionalValue<Map<ConversationChannel, ChannelSpecificMessage>>> {

public ChannelSpecificMessageMapSerializer() {
this(null);
}

public ChannelSpecificMessageMapSerializer(
Class<OptionalValue<Map<ConversationChannel, ChannelSpecificMessage>>> t) {
super(t);
}

@Override
public void serialize(
OptionalValue<Map<ConversationChannel, ChannelSpecificMessage>> raw,
JsonGenerator jgen,
SerializerProvider provider)
throws IOException {

if (null == raw || !raw.isPresent()) {
return;
}

Map<ConversationChannel, ChannelSpecificMessage> value = raw.get();
if (null == value) {
jgen.writeObject(null);
return;
}

Map<ConversationChannel, ChannelSpecificMessageInternal> out = new HashMap<>(value.size());
value.forEach(
(channel, message) -> {
ChannelSpecificMessageMessageInternalImpl internal =
new ChannelSpecificMessageMessageInternalImpl();
internal.setActualInstance(message);

MessageTypeEnum type = null;
ChannelSpecificMessageInternal.Builder builder =
ChannelSpecificMessageInternal.builder();
if (message instanceof FlowChannelSpecificMessage) {
type = MessageTypeEnum.FLOWS;
} else {
// do not throw exception to not break all schema's serialization
LOGGER.log(Level.SEVERE, "Unexpected message: {}", message);
}
builder.setMessageType(type).setMessage(internal);
out.put(channel, builder.build());
});

jgen.writeObject(out);
}
}
}
Loading

0 comments on commit 28b8db8

Please sign in to comment.