diff --git a/README.md b/README.md index 5c74ff7b3..50dd691a6 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Note: Replace VERSION-YOU-WANT-TO-BE-USED by a Jackson version of a ... ``` -## Changelog & Migration +## Changelog & Migration For information about the latest changes in the SDK, please refer to the [CHANGELOG](CHANGELOG.md) file and the [MIGRATION-GUIDE](MIGRATION-GUIDE.md) for instructions on how to update your code when upgrading to a new major version of the SDK. diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 164998a4a..9e7897162 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -57,12 +57,12 @@ public class SinchClient { private final Configuration configuration; - private NumbersService numbers; - private SMSService sms; - private VerificationService verification; - private VoiceService voice; - private ConversationService conversation; - private HttpClientApache httpClient; + private volatile NumbersService numbers; + private volatile SMSService sms; + private volatile VerificationService verification; + private volatile VoiceService voice; + private volatile ConversationService conversation; + private volatile HttpClientApache httpClient; /** * Create a Sinch Client instance based onto configuration @@ -247,7 +247,11 @@ public Configuration getConfiguration() { */ public NumbersService numbers() { if (null == numbers) { - numbers = numbersInit(); + synchronized (this) { + if (null == numbers) { + numbers = numbersInit(); + } + } } return numbers; } @@ -262,7 +266,11 @@ public NumbersService numbers() { */ public SMSService sms() { if (null == sms) { - sms = smsInit(); + synchronized (this) { + if (null == sms) { + sms = smsInit(); + } + } } return sms; } @@ -277,7 +285,11 @@ public SMSService sms() { */ public VerificationService verification() { if (null == verification) { - verification = verificationInit(); + synchronized (this) { + if (null == verification) { + verification = verificationInit(); + } + } } return verification; } @@ -292,7 +304,11 @@ public VerificationService verification() { */ public VoiceService voice() { if (null == voice) { - voice = voiceInit(); + synchronized (this) { + if (null == voice) { + voice = voiceInit(); + } + } } return voice; } @@ -307,7 +323,11 @@ public VoiceService voice() { */ public ConversationService conversation() { if (null == conversation) { - conversation = conversationInit(); + synchronized (this) { + if (null == conversation) { + conversation = conversationInit(); + } + } } return conversation; } @@ -375,21 +395,28 @@ private Properties handlePropertiesFile(String fileName) { } private HttpClientApache getHttpClient() { - if (null == httpClient || httpClient.isClosed()) { - // TODO: by adding a setter, we could imagine having another HTTP client provided - // programmatically or use - // configuration file referencing another class by name - this.httpClient = new HttpClientApache(); - - // set SDK User-Agent - String userAgent = formatSdkUserAgentHeader(); - this.httpClient.setRequestHeaders( - Stream.of(new String[][] {{SDK_USER_AGENT_HEADER, userAgent}}) - .collect(Collectors.toMap(data -> data[0], data -> data[1]))); - - LOGGER.finest(String.format("HTTP client loaded (%s='%s'", SDK_USER_AGENT_HEADER, userAgent)); + HttpClientApache local = httpClient; + if (null == local || local.isClosed()) { + synchronized (this) { + local = httpClient; + if (null == local || local.isClosed()) { + // TODO: by adding a setter, we could imagine having another HTTP client provided + // programmatically or use configuration file referencing another class by name + local = new HttpClientApache(); + + // set SDK User-Agent + String userAgent = formatSdkUserAgentHeader(); + local.setRequestHeaders( + Stream.of(new String[][] {{SDK_USER_AGENT_HEADER, userAgent}}) + .collect(Collectors.toMap(data -> data[0], data -> data[1]))); + + LOGGER.finest( + String.format("HTTP client loaded (%s='%s'", SDK_USER_AGENT_HEADER, userAgent)); + this.httpClient = local; + } + } } - return this.httpClient; + return local; } private String formatSdkUserAgentHeader() { diff --git a/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java b/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java index c7fdc53e8..5061d481b 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/adapters/ConversationService.java @@ -14,8 +14,8 @@ public class ConversationService implements com.sinch.sdk.domains.conversation.C private final ServerConfiguration oAuthServer; private final Supplier httpClientSupplier; - private com.sinch.sdk.domains.conversation.api.v1.ConversationService conversationV1; - private TemplatesService templates; + private volatile com.sinch.sdk.domains.conversation.api.v1.ConversationService conversationV1; + private volatile TemplatesService templates; public ConversationService( UnifiedCredentials credentials, @@ -30,17 +30,25 @@ public ConversationService( public com.sinch.sdk.domains.conversation.api.v1.ConversationService v1() { if (null == this.conversationV1) { - this.conversationV1 = - new com.sinch.sdk.domains.conversation.api.v1.adapters.ConversationService( - credentials, context, oAuthServer, httpClientSupplier); + synchronized (this) { + if (null == this.conversationV1) { + this.conversationV1 = + new com.sinch.sdk.domains.conversation.api.v1.adapters.ConversationService( + credentials, context, oAuthServer, httpClientSupplier); + } + } } - return this.conversationV1; } public TemplatesService templates() { if (null == this.templates) { - this.templates = new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + synchronized (this) { + if (null == this.templates) { + this.templates = + new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + } + } } return this.templates; } diff --git a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/SinchEventsService.java b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/SinchEventsService.java index 91acf52b1..e8863ae54 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/SinchEventsService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/SinchEventsService.java @@ -11,7 +11,7 @@ * callback URL * *

see online + * href="https://developers.sinch.com/docs/conversation/callbacks#conversation-api-callbacks">online * documentation * * @since 2.0 diff --git a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java index c0b896aa7..ce9c0160d 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/api/v1/adapters/ConversationService.java @@ -96,140 +96,185 @@ public ConversationService( public AppsService apps() { if (null == this.apps) { - instanceLazyInit(); - this.apps = - new AppsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.apps) { + instanceLazyInit(); + this.apps = + new AppsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.apps; } public ContactsService contacts() { if (null == this.contacts) { - instanceLazyInit(); - this.contacts = - new ContactsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.contacts) { + instanceLazyInit(); + this.contacts = + new ContactsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.contacts; } public MessagesService messages() { if (null == this.messages) { - instanceLazyInit(); - this.messages = - new MessagesServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.messages) { + instanceLazyInit(); + this.messages = + new MessagesServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.messages; } public ConversationsService conversations() { if (null == this.conversations) { - instanceLazyInit(); - this.conversations = - new ConversationsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.conversations) { + instanceLazyInit(); + this.conversations = + new ConversationsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.conversations; } public EventsService events() { if (null == this.events) { - instanceLazyInit(); - this.events = - new EventsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.events) { + instanceLazyInit(); + this.events = + new EventsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.events; } public TranscodingService transcoding() { if (null == this.transcoding) { - instanceLazyInit(); - this.transcoding = - new TranscodingServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.transcoding) { + instanceLazyInit(); + this.transcoding = + new TranscodingServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.transcoding; } public CapabilityService capability() { if (null == this.capability) { - instanceLazyInit(); - this.capability = - new CapabilityServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.capability) { + instanceLazyInit(); + this.capability = + new CapabilityServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.capability; } public TemplatesService templates() { if (null == this.templates) { - this.templates = new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + synchronized (this) { + if (null == this.templates) { + this.templates = + new TemplatesService(credentials, context, oAuthServer, httpClientSupplier); + } + } } return this.templates; } public EventDestinationsService eventDestinations() { if (null == this.eventDestinations) { - instanceLazyInit(); - this.eventDestinations = - new EventDestinationsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.eventDestinations) { + instanceLazyInit(); + this.eventDestinations = + new EventDestinationsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.eventDestinations; } public SinchEventsService sinchEvents() { if (null == this.sinchEvents) { - this.sinchEvents = new SinchEventsService(new HmacAuthenticationValidation()); + synchronized (this) { + if (null == this.sinchEvents) { + this.sinchEvents = new SinchEventsService(new HmacAuthenticationValidation()); + } + } } return this.sinchEvents; } public ProjectSettingsService projectSettings() { if (null == this.projectSettings) { - instanceLazyInit(); - this.projectSettings = - new ProjectSettingsServiceImpl( - httpClientSupplier.get(), - context.getServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.projectSettings) { + instanceLazyInit(); + this.projectSettings = + new ProjectSettingsServiceImpl( + httpClientSupplier.get(), + context.getServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.projectSettings; } diff --git a/client/src/main/com/sinch/sdk/domains/conversation/models/v1/contacts/package-info.java b/client/src/main/com/sinch/sdk/domains/conversation/models/v1/contacts/package-info.java index 96d91f908..45ab94c66 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/models/v1/contacts/package-info.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/models/v1/contacts/package-info.java @@ -9,7 +9,7 @@ * management processes. However, you can also use API calls to manually manage your contacts. * * @see Capability + * href="https://developers.sinch.com/docs/conversation/api-reference/conversation/tag/Contact">Contacts * @since 1.3 */ package com.sinch.sdk.domains.conversation.models.v1.contacts; diff --git a/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java b/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java index a06d58981..021cef5dd 100644 --- a/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java +++ b/client/src/main/com/sinch/sdk/domains/conversation/templates/api/adapters/TemplatesService.java @@ -56,14 +56,18 @@ public TemplatesService( public TemplatesV2Service v2() { if (null == this.v2) { - instanceLazyInit(); - this.v2 = - new TemplatesV2ServiceImpl( - httpClientSupplier.get(), - context.getTemplateManagementServer(), - authManagers, - HttpMapper.getInstance(), - this.uriUUID); + synchronized (this) { + if (null == this.v2) { + instanceLazyInit(); + this.v2 = + new TemplatesV2ServiceImpl( + httpClientSupplier.get(), + context.getTemplateManagementServer(), + authManagers, + HttpMapper.getInstance(), + this.uriUUID); + } + } } return this.v2; } diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java index 397d2d94c..eff4ba206 100644 --- a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java +++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java @@ -8,20 +8,34 @@ public class NumbersService implements com.sinch.sdk.domains.numbers.NumbersService { - private final com.sinch.sdk.domains.numbers.api.v1.NumbersService v1; + private final UnifiedCredentials credentials; + private final NumbersContext context; + private final ServerConfiguration oAuthServer; + private final Supplier httpClientSupplier; + + private volatile com.sinch.sdk.domains.numbers.api.v1.NumbersService v1; public NumbersService( UnifiedCredentials credentials, NumbersContext context, ServerConfiguration oAuthServer, Supplier httpClientSupplier) { - - this.v1 = - new com.sinch.sdk.domains.numbers.api.v1.adapters.NumbersService( - credentials, context, oAuthServer, httpClientSupplier); + this.credentials = credentials; + this.context = context; + this.oAuthServer = oAuthServer; + this.httpClientSupplier = httpClientSupplier; } public com.sinch.sdk.domains.numbers.api.v1.NumbersService v1() { - return v1; + if (null == this.v1) { + synchronized (this) { + if (null == this.v1) { + this.v1 = + new com.sinch.sdk.domains.numbers.api.v1.adapters.NumbersService( + credentials, context, oAuthServer, httpClientSupplier); + } + } + } + return this.v1; } } diff --git a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java index 7cc68e8d3..fbab255b7 100644 --- a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java +++ b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/NumbersService.java @@ -66,54 +66,74 @@ public NumbersService( AvailableNumberServiceFacade available() { if (null == this.available) { - instanceLazyInit(); - this.available = - new AvailableNumberServiceFacade(uriUUID, context, httpClientSupplier, authManagers); + synchronized (this) { + if (null == this.available) { + instanceLazyInit(); + this.available = + new AvailableNumberServiceFacade(uriUUID, context, httpClientSupplier, authManagers); + } + } } return this.available; } public AvailableRegionsService regions() { if (null == this.regions) { - instanceLazyInit(); - this.regions = - new AvailableRegionsServiceImpl( - httpClientSupplier.get(), - context.getNumbersServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.regions) { + instanceLazyInit(); + this.regions = + new AvailableRegionsServiceImpl( + httpClientSupplier.get(), + context.getNumbersServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.regions; } ActiveNumberServiceFacade active() { if (null == this.active) { - instanceLazyInit(); - this.active = - new ActiveNumberServiceFacade(uriUUID, this, context, httpClientSupplier, authManagers); + synchronized (this) { + if (null == this.active) { + instanceLazyInit(); + this.active = + new ActiveNumberServiceFacade( + uriUUID, this, context, httpClientSupplier, authManagers); + } + } } return this.active; } public EventDestinationsService eventDestinations() { if (null == this.eventDestinations) { - instanceLazyInit(); - this.eventDestinations = - new EventDestinationsServiceImpl( - httpClientSupplier.get(), - context.getNumbersServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.eventDestinations) { + instanceLazyInit(); + this.eventDestinations = + new EventDestinationsServiceImpl( + httpClientSupplier.get(), + context.getNumbersServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.eventDestinations; } public SinchEventsService sinchEvents() { - if (null == this.sinchEvents) { - this.sinchEvents = new SinchEventsService(new SinchEventsAuthenticationValidation()); + synchronized (this) { + if (null == this.sinchEvents) { + this.sinchEvents = new SinchEventsService(new SinchEventsAuthenticationValidation()); + } + } } return this.sinchEvents; } diff --git a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/SinchEventsAuthenticationValidation.java b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/SinchEventsAuthenticationValidation.java index f265bd690..5afabd3e4 100644 --- a/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/SinchEventsAuthenticationValidation.java +++ b/client/src/main/com/sinch/sdk/domains/numbers/api/v1/adapters/SinchEventsAuthenticationValidation.java @@ -38,8 +38,8 @@ private String encode(String secret, String stringToSign) { new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1_ALGORITHM); Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); mac.init(secretKeySpec); - byte[] hmacSha256 = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); - return SinchEventsAuthenticationValidation.bytesToHex(hmacSha256); + byte[] hmacSHA1 = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); + return SinchEventsAuthenticationValidation.bytesToHex(hmacSHA1); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new ApiAuthException(e); } diff --git a/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java b/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java index 11503c42e..c178e9df8 100644 --- a/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java +++ b/client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java @@ -9,31 +9,40 @@ public class SMSService implements com.sinch.sdk.domains.sms.SMSService { - private final com.sinch.sdk.domains.sms.api.v1.SMSService v1; + private final Supplier v1Initializer; + + private volatile com.sinch.sdk.domains.sms.api.v1.SMSService v1; public SMSService( UnifiedCredentials credentials, SmsContext context, ServerConfiguration oAuthServer, Supplier httpClientSupplier) { - - this.v1 = - new com.sinch.sdk.domains.sms.api.v1.adapters.SMSService( - credentials, context, oAuthServer, httpClientSupplier); + this.v1Initializer = + () -> + new com.sinch.sdk.domains.sms.api.v1.adapters.SMSService( + credentials, context, oAuthServer, httpClientSupplier); } public SMSService( SmsServicePlanCredentials credentials, SmsContext context, Supplier httpClientSupplier) { - - this.v1 = - new com.sinch.sdk.domains.sms.api.v1.adapters.SMSService( - credentials, context, httpClientSupplier); + this.v1Initializer = + () -> + new com.sinch.sdk.domains.sms.api.v1.adapters.SMSService( + credentials, context, httpClientSupplier); } @Override public com.sinch.sdk.domains.sms.api.v1.SMSService v1() { + if (null == this.v1) { + synchronized (this) { + if (null == this.v1) { + this.v1 = v1Initializer.get(); + } + } + } return this.v1; } } diff --git a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java index 941a84ee1..3ab3ebba5 100644 --- a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java +++ b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SMSService.java @@ -66,14 +66,18 @@ public SMSService( @Override public BatchesService batches() { if (null == this.batches) { - instanceLazyInit(); - this.batches = - new BatchesServiceImpl( - httpClientSupplier.get(), - context.getSmsServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.batches) { + instanceLazyInit(); + this.batches = + new BatchesServiceImpl( + httpClientSupplier.get(), + context.getSmsServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.batches; } @@ -81,14 +85,18 @@ public BatchesService batches() { @Override public InboundsService inbounds() { if (null == this.inbounds) { - instanceLazyInit(); - this.inbounds = - new InboundsServiceImpl( - httpClientSupplier.get(), - context.getSmsServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.inbounds) { + instanceLazyInit(); + this.inbounds = + new InboundsServiceImpl( + httpClientSupplier.get(), + context.getSmsServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.inbounds; } @@ -96,14 +104,18 @@ public InboundsService inbounds() { @Override public DeliveryReportsService deliveryReports() { if (null == this.deliveryReports) { - instanceLazyInit(); - this.deliveryReports = - new DeliveryReportsServiceImpl( - httpClientSupplier.get(), - context.getSmsServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.deliveryReports) { + instanceLazyInit(); + this.deliveryReports = + new DeliveryReportsServiceImpl( + httpClientSupplier.get(), + context.getSmsServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.deliveryReports; } @@ -111,14 +123,18 @@ public DeliveryReportsService deliveryReports() { @Override public GroupsService groups() { if (null == this.groups) { - instanceLazyInit(); - this.groups = - new GroupsServiceImpl( - httpClientSupplier.get(), - context.getSmsServer(), - authManagers, - HttpMapper.getInstance(), - uriUUID); + synchronized (this) { + if (null == this.groups) { + instanceLazyInit(); + this.groups = + new GroupsServiceImpl( + httpClientSupplier.get(), + context.getSmsServer(), + authManagers, + HttpMapper.getInstance(), + uriUUID); + } + } } return this.groups; } @@ -126,7 +142,11 @@ public GroupsService groups() { @Override public SinchEventsService sinchEvents() { if (null == this.sinchEvents) { - this.sinchEvents = new SinchEventsService(new HmacAuthenticationValidation()); + synchronized (this) { + if (null == this.sinchEvents) { + this.sinchEvents = new SinchEventsService(new HmacAuthenticationValidation()); + } + } } return this.sinchEvents; } diff --git a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SinchEventsService.java b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SinchEventsService.java index e97b0b1f9..17ef68352 100644 --- a/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SinchEventsService.java +++ b/client/src/main/com/sinch/sdk/domains/sms/api/v1/adapters/SinchEventsService.java @@ -15,15 +15,17 @@ public SinchEventsService(HmacAuthenticationValidation authenticationChecker) { this.authenticationChecker = authenticationChecker; } + @Override public SmsSinchEvent parseEvent(String jsonPayload) throws ApiMappingException { try { return Mapper.getInstance().readValue(jsonPayload, SmsSinchEvent.class); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new ApiMappingException(jsonPayload, e); } } + @Override public boolean validateAuthenticationHeader( String secret, Map headers, String jsonPayload) { diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java index 5eb6d860a..c1947b4ca 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java @@ -7,19 +7,31 @@ public class VerificationService implements com.sinch.sdk.domains.verification.VerificationService { - private final com.sinch.sdk.domains.verification.api.v1.VerificationService v1; + private final ApplicationCredentials credentials; + private final VerificationContext context; + private final Supplier httpClientSupplier; + + private volatile com.sinch.sdk.domains.verification.api.v1.VerificationService v1; public VerificationService( ApplicationCredentials credentials, VerificationContext context, Supplier httpClientSupplier) { - - this.v1 = - new com.sinch.sdk.domains.verification.api.v1.adapters.VerificationService( - credentials, context, httpClientSupplier); + this.credentials = credentials; + this.context = context; + this.httpClientSupplier = httpClientSupplier; } public com.sinch.sdk.domains.verification.api.v1.VerificationService v1() { + if (null == this.v1) { + synchronized (this) { + if (null == this.v1) { + this.v1 = + new com.sinch.sdk.domains.verification.api.v1.adapters.VerificationService( + credentials, context, httpClientSupplier); + } + } + } return this.v1; } } diff --git a/client/src/main/com/sinch/sdk/domains/verification/api/v1/adapters/VerificationService.java b/client/src/main/com/sinch/sdk/domains/verification/api/v1/adapters/VerificationService.java index 261897728..e189f365a 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/api/v1/adapters/VerificationService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/api/v1/adapters/VerificationService.java @@ -71,63 +71,81 @@ private void createAuthManagers(ApplicationCredentials credentials) { // hidden behind the "Basic" keyword // We need both auth manager to handle Sinch Events because of customer will choose from his // dashboard which scheme to be used - clientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - clientAuthManagers.put( + Map localClientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + localClientAuthManagers.put( BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION, useApplicationAuth ? applicationAuthManager : basicAuthManager); + clientAuthManagers = localClientAuthManagers; // here we need both auth managers to handle Sinch Events because we are receiving an - // Authorization - // header with "Application" keyword - sinchEventsAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - sinchEventsAuthManagers.put(BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION, basicAuthManager); - sinchEventsAuthManagers.put( + // Authorization header with "Application" keyword + Map localSinchEventsAuthManagers = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + localSinchEventsAuthManagers.put(BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION, basicAuthManager); + localSinchEventsAuthManagers.put( APPLICATION_SECURITY_SCHEME_KEYWORD_VERIFICATION, applicationAuthManager); + sinchEventsAuthManagers = localSinchEventsAuthManagers; } public VerificationsStartService verificationStart() { if (null == this.startService) { - instanceLazyInit(); - this.startService = - new VerificationsStartServiceImpl( - httpClientSupplier.get(), - context.getVerificationServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.startService) { + instanceLazyInit(); + this.startService = + new VerificationsStartServiceImpl( + httpClientSupplier.get(), + context.getVerificationServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.startService; } public VerificationsReportService verificationReport() { if (null == this.reportService) { - instanceLazyInit(); - this.reportService = - new VerificationsReportServiceImpl( - httpClientSupplier.get(), - context.getVerificationServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.reportService) { + instanceLazyInit(); + this.reportService = + new VerificationsReportServiceImpl( + httpClientSupplier.get(), + context.getVerificationServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.reportService; } public VerificationsStatusService verificationStatus() { if (null == this.statusService) { - instanceLazyInit(); - this.statusService = - new VerificationsStatusServiceImpl( - httpClientSupplier.get(), - context.getVerificationServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.statusService) { + instanceLazyInit(); + this.statusService = + new VerificationsStatusServiceImpl( + httpClientSupplier.get(), + context.getVerificationServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.statusService; } public SinchEventsService sinchEvents() { if (null == this.sinchEvents) { - instanceLazyInit(); - this.sinchEvents = new SinchEventsService(sinchEventsAuthManagers); + synchronized (this) { + if (null == this.sinchEvents) { + instanceLazyInit(); + this.sinchEvents = new SinchEventsService(sinchEventsAuthManagers); + } + } } return this.sinchEvents; } diff --git a/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java index 21af4dc0f..8037bf697 100644 --- a/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java +++ b/client/src/main/com/sinch/sdk/domains/voice/adapters/VoiceService.java @@ -7,19 +7,31 @@ public class VoiceService implements com.sinch.sdk.domains.voice.VoiceService { - private final com.sinch.sdk.domains.voice.api.v1.VoiceService v1; + private final ApplicationCredentials credentials; + private final VoiceContext context; + private final Supplier httpClientSupplier; + + private volatile com.sinch.sdk.domains.voice.api.v1.VoiceService v1; public VoiceService( ApplicationCredentials credentials, VoiceContext context, Supplier httpClientSupplier) { - - this.v1 = - new com.sinch.sdk.domains.voice.api.v1.adapters.VoiceService( - credentials, context, httpClientSupplier); + this.credentials = credentials; + this.context = context; + this.httpClientSupplier = httpClientSupplier; } public com.sinch.sdk.domains.voice.api.v1.VoiceService v1() { + if (null == this.v1) { + synchronized (this) { + if (null == this.v1) { + this.v1 = + new com.sinch.sdk.domains.voice.api.v1.adapters.VoiceService( + credentials, context, httpClientSupplier); + } + } + } return this.v1; } } diff --git a/client/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/VoiceService.java b/client/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/VoiceService.java index 2b94788b0..38d3a45f1 100644 --- a/client/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/VoiceService.java +++ b/client/src/main/com/sinch/sdk/domains/voice/api/v1/adapters/VoiceService.java @@ -40,7 +40,7 @@ public class VoiceService implements com.sinch.sdk.domains.voice.api.v1.VoiceSer private volatile SinchEventsService sinchEvents; private volatile Map clientAuthManagers; - private volatile Map webhooksAuthManagers; + private volatile Map sinchEventsAuthManagers; static { LocalLazyInit.init(); @@ -64,82 +64,104 @@ private void createAuthManagers(ApplicationCredentials credentials) { new ApplicationAuthManager( credentials.getApplicationKey(), credentials.getApplicationSecret()); - clientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - clientAuthManagers.put(SECURITY_SCHEME_KEYWORD, applicationAuthManager); + Map localClientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + localClientAuthManagers.put(SECURITY_SCHEME_KEYWORD, applicationAuthManager); + clientAuthManagers = localClientAuthManagers; - // here we need both auth managers to handle webhooks because we are receiving an Authorization - // header with "Application" keyword - webhooksAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - webhooksAuthManagers.put(BASIC_SECURITY_SCHEME_KEYWORD, basicAuthManager); - webhooksAuthManagers.put(APPLICATION_SECURITY_SCHEME_KEYWORD, applicationAuthManager); + // here we need both auth managers to handle Sinch Events because we are receiving an + // Authorization header with "Application" keyword + Map sinchEventsAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + sinchEventsAuthManagers.put(BASIC_SECURITY_SCHEME_KEYWORD, basicAuthManager); + sinchEventsAuthManagers.put(APPLICATION_SECURITY_SCHEME_KEYWORD, applicationAuthManager); + this.sinchEventsAuthManagers = sinchEventsAuthManagers; } public CalloutsService callouts() { if (null == this.callouts) { - instanceLazyInit(); - this.callouts = - new CalloutsServiceImpl( - httpClientSupplier.get(), - context.getVoiceServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.callouts) { + instanceLazyInit(); + this.callouts = + new CalloutsServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.callouts; } public ConferencesService conferences() { if (null == this.conferences) { - instanceLazyInit(); - this.conferences = - new ConferencesServiceImpl( - httpClientSupplier.get(), - context.getVoiceServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.conferences) { + instanceLazyInit(); + this.conferences = + new ConferencesServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.conferences; } public CallsService calls() { if (null == this.calls) { - instanceLazyInit(); - this.calls = - new CallsServiceImpl( - httpClientSupplier.get(), - context.getVoiceServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.calls) { + instanceLazyInit(); + this.calls = + new CallsServiceImpl( + httpClientSupplier.get(), + context.getVoiceServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.calls; } public ApplicationsService applications() { if (null == this.applications) { - instanceLazyInit(); - this.applications = - new ApplicationsServiceImpl( - httpClientSupplier.get(), - context.getVoiceApplicationManagementServer(), - clientAuthManagers, - HttpMapper.getInstance()); + synchronized (this) { + if (null == this.applications) { + instanceLazyInit(); + this.applications = + new ApplicationsServiceImpl( + httpClientSupplier.get(), + context.getVoiceApplicationManagementServer(), + clientAuthManagers, + HttpMapper.getInstance()); + } + } } return this.applications; } public SinchEventsService sinchEvents() { if (null == this.sinchEvents) { - instanceLazyInit(); - this.sinchEvents = new SinchEventsService(webhooksAuthManagers); + synchronized (this) { + if (null == this.sinchEvents) { + instanceLazyInit(); + this.sinchEvents = new SinchEventsService(sinchEventsAuthManagers); + } + } } return this.sinchEvents; } private void instanceLazyInit() { - if (null != this.clientAuthManagers && null != this.webhooksAuthManagers) { + if (null != this.clientAuthManagers && null != this.sinchEventsAuthManagers) { return; } synchronized (this) { - if (null == this.clientAuthManagers || null == this.webhooksAuthManagers) { + if (null == this.clientAuthManagers || null == this.sinchEventsAuthManagers) { // Currently, we are not supporting unified credentials: ensure application credentials are // defined diff --git a/client/src/main/com/sinch/sdk/http/HttpClientApache.java b/client/src/main/com/sinch/sdk/http/HttpClientApache.java index 2e96027f9..ed4918628 100644 --- a/client/src/main/com/sinch/sdk/http/HttpClientApache.java +++ b/client/src/main/com/sinch/sdk/http/HttpClientApache.java @@ -49,7 +49,7 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient { private Map headersToBeAdded; - private CloseableHttpClient client; + private volatile CloseableHttpClient client; public HttpClientApache() { this.client = HttpClients.createDefault(); diff --git a/core/src/main/com/sinch/sdk/core/utils/MapUtils.java b/core/src/main/com/sinch/sdk/core/utils/MapUtils.java index 0bcd400cc..eaaaa6078 100644 --- a/core/src/main/com/sinch/sdk/core/utils/MapUtils.java +++ b/core/src/main/com/sinch/sdk/core/utils/MapUtils.java @@ -12,11 +12,14 @@ public class MapUtils { */ public static Map getCaseInsensitiveMap(Map headers) { - // convert header keys to use case-insensitive map keys + // convert header keys to use case-insensitive map keys, skipping null keys Map caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - // ensure no null key value - headers.entrySet().removeIf(entry -> Objects.isNull(entry.getKey())); - caseInsensitiveHeaders.putAll(headers); + headers.forEach( + (key, value) -> { + if (!Objects.isNull(key)) { + caseInsensitiveHeaders.put(key, value); + } + }); return caseInsensitiveHeaders; } }