Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# onesignal-java-client

OneSignal
- API version: 5.6.0
- API version: 5.7.0

A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com

Expand All @@ -19,14 +19,14 @@ Building the API client library requires:
<dependency>
<groupId>com.onesignal</groupId>
<artifactId>onesignal-java-client</artifactId>
<version>5.6.0</version>
<version>5.7.0</version>
</dependency>
```

### Gradle

```groovy
implementation "com.onesignal:onesignal-java-client:5.6.0"
implementation "com.onesignal:onesignal-java-client:5.7.0"
```

## Configuration
Expand Down
2 changes: 1 addition & 1 deletion api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ info:
customer engagement strategies. Learn more at onesignal.com
termsOfService: https://onesignal.com/tos
title: OneSignal
version: 5.6.0
version: 5.7.0
servers:
- url: https://api.onesignal.com
paths:
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ apply plugin: 'com.diffplug.spotless'
apply plugin: 'com.vanniktech.maven.publish'

group = 'com.onesignal'
version = '5.6.0'
version = '5.7.0'

buildscript {
repositories {
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ lazy val root = (project in file(".")).
settings(
organization := "com.onesignal",
name := "onesignal-java-client",
version := "5.6.0",
version := "5.7.0",
scalaVersion := "2.11.4",
scalacOptions ++= Seq("-feature"),
javacOptions in compile ++= Seq("-Xlint:deprecation"),
Expand Down
196 changes: 195 additions & 1 deletion docs/DefaultApi.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<artifactId>onesignal-java-client</artifactId>
<packaging>jar</packaging>
<name>onesignal-java-client</name>
<version>5.6.0</version>
<version>5.7.0</version>
<url>https://github.com/OneSignal/onesignal-java-api</url>
<description>OneSignal Java API Client</description>
<scm>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/onesignal/client/ApiCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/onesignal/client/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down Expand Up @@ -133,7 +133,7 @@ private void init() {
json = new JSON();

// Set default User-Agent.
setUserAgent("OpenAPI-Generator/5.6.0/java");
setUserAgent("OpenAPI-Generator/5.7.0/java");

authentications = new HashMap<String, Authentication>();
}
Expand Down
80 changes: 79 additions & 1 deletion src/main/java/com/onesignal/client/ApiException.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand All @@ -13,6 +13,12 @@

package com.onesignal.client;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.List;

Expand Down Expand Up @@ -163,4 +169,76 @@ public String getMessage() {
return String.format("Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s",
super.getMessage(), this.getCode(), this.getResponseBody(), this.getResponseHeaders());
}

/**
* Get the error messages carried by the response body, normalized to a flat
* list of strings regardless of which envelope shape the API returned
* ({@code {"errors": "..."}}, {@code {"errors": ["..."]}},
* {@code {"errors": [{"code": ..., "title": ...}]}}, or an object map such as
* {@code {"errors": {"invalid_aliases": {...}}}}, surfaced as
* {@code "<key>: <value>"} entries). Returns an empty list when the body is
* not a recognizable error envelope. The raw body remains available via
* {@link #getResponseBody()}.
*
* @return the normalized error messages
*/
public List<String> getErrorMessages() {
List<String> messages = new ArrayList<String>();
if (responseBody == null || responseBody.isEmpty()) {
return messages;
}

try {
JsonElement root = JsonParser.parseString(responseBody);
if (!root.isJsonObject()) {
return messages;
}

JsonElement errors = root.getAsJsonObject().get("errors");
if (errors == null || errors.isJsonNull()) {
return messages;
}

if (errors.isJsonPrimitive()) {
if (errors.getAsJsonPrimitive().isString()) {
messages.add(errors.getAsString());
}
} else if (errors.isJsonArray()) {
for (JsonElement item : errors.getAsJsonArray()) {
if (item.isJsonPrimitive()) {
if (item.getAsJsonPrimitive().isString()) {
messages.add(item.getAsString());
}
} else if (item.isJsonObject()) {
JsonObject object = item.getAsJsonObject();
JsonElement title = object.get("title");
JsonElement code = object.get("code");
if (title != null && title.isJsonPrimitive() && title.getAsJsonPrimitive().isString()
&& !title.getAsString().isEmpty()) {
messages.add(title.getAsString());
} else if (code != null && !code.isJsonNull()) {
messages.add(code.isJsonPrimitive() ? code.getAsString() : code.toString());
}
}
}
} else if (errors.isJsonObject()) {
// Object-shaped envelopes (e.g. {"invalid_aliases": {...}}) carry
// data under arbitrary keys; surface each so it isn't silently
// dropped. Key order is unspecified, so sort for deterministic output.
JsonObject object = errors.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
JsonElement value = entry.getValue();
String rendered = value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()
? value.getAsString()
: value.toString();
messages.add(entry.getKey() + ": " + rendered);
}
Collections.sort(messages);
}
} catch (RuntimeException e) {
return messages;
}

return messages;
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/onesignal/client/ApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/onesignal/client/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/onesignal/client/JSON.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* OneSignal
* A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com
*
* The version of the OpenAPI document: 5.6.0
* The version of the OpenAPI document: 5.7.0
* Contact: devrel@onesignal.com
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Expand Down
150 changes: 150 additions & 0 deletions src/main/java/com/onesignal/client/NotificationHelpers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.onesignal.client;

import com.onesignal.client.api.DefaultApi;
import com.onesignal.client.model.CreateNotificationSuccessResponse;
import com.onesignal.client.model.Notification;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* Helpers for common OneSignal API usage patterns.
*/
public final class NotificationHelpers {
public static final int DEFAULT_MAX_RETRIES = 3;
public static final long DEFAULT_BASE_DELAY_MILLIS = 1000L;
public static final long MIN_BASE_DELAY_MILLIS = 1000L;
public static final long MAX_BASE_DELAY_MILLIS = 60000L;

private NotificationHelpers() {
}

/**
* Result of {@link #createNotificationWithRetry}: the create response plus
* whether the server replayed a previously completed request
* (Idempotent-Replayed response header).
*/
public static final class CreateNotificationWithRetryResult {
private final CreateNotificationSuccessResponse response;
private final boolean wasReplayed;

CreateNotificationWithRetryResult(CreateNotificationSuccessResponse response, boolean wasReplayed) {
this.response = response;
this.wasReplayed = wasReplayed;
}

public CreateNotificationSuccessResponse getResponse() {
return response;
}

public boolean getWasReplayed() {
return wasReplayed;
}
}

/**
* Same as {@link #createNotificationWithRetry(DefaultApi, Notification, int, long)}
* with the default retry budget (3 retries, 1s backoff base).
*
* @param api the API instance to call through
* @param notification the notification to send
* @return the response plus the replay flag
* @throws ApiException when the call fails with a non-retryable error or the retry budget is exhausted
*/
public static CreateNotificationWithRetryResult createNotificationWithRetry(DefaultApi api, Notification notification) throws ApiException {
return createNotificationWithRetry(api, notification, DEFAULT_MAX_RETRIES, DEFAULT_BASE_DELAY_MILLIS);
}

/**
* Create a notification with safe, idempotent retries.
*
* <p>Ensures {@code notification.idempotencyKey} is set (generating a
* UUIDv4 when absent) so the server can deduplicate, then calls
* {@code createNotification}. Transient failures (HTTP 429, HTTP 503, or
* IO-level errors) are retried with the SAME idempotency key, honoring the
* {@code Retry-After} response header when present and falling back to
* exponential backoff ({@code baseDelayMillis * 2^attempt}) otherwise.
* Other errors are thrown immediately.
*
* @param api the API instance to call through
* @param notification an existing idempotency key is respected, never overwritten
* @param maxRetries retries after the initial attempt
* @param baseDelayMillis backoff base in milliseconds when Retry-After is absent; clamped to [1000, 60000]
* @return the response plus the replay flag
* @throws ApiException when the call fails with a non-retryable error or the retry budget is exhausted
*/
public static CreateNotificationWithRetryResult createNotificationWithRetry(DefaultApi api, Notification notification, int maxRetries, long baseDelayMillis) throws ApiException {
if (notification.getIdempotencyKey() == null || notification.getIdempotencyKey().isEmpty()) {
notification.setIdempotencyKey(UUID.randomUUID().toString());
}

// Clamp the backoff base so a stray value can neither hammer the API
// (too small) nor stall the caller for an unbounded stretch (too large).
long effectiveBaseDelay = Math.min(MAX_BASE_DELAY_MILLIS, Math.max(MIN_BASE_DELAY_MILLIS, baseDelayMillis));

int attempt = 0;
while (true) {
try {
ApiResponse<CreateNotificationSuccessResponse> response = api.createNotificationWithHttpInfo(notification);
return new CreateNotificationWithRetryResult(response.getData(), isReplayed(response.getHeaders()));
} catch (ApiException e) {
// Code 0 with an IOException cause is a transport-level failure
// (timeout, connection reset) that never reached the server.
boolean retryable = e.getCode() == 429 || e.getCode() == 503
|| (e.getCode() == 0 && e.getCause() instanceof IOException);
if (!retryable || attempt >= maxRetries) {
throw e;
}
sleep(retryDelayMillis(e.getResponseHeaders(), attempt, effectiveBaseDelay));
attempt++;
}
}
}

private static String headerValue(Map<String, List<String>> headers, String name) {
if (headers == null) {
return null;
}
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(name)
&& entry.getValue() != null && !entry.getValue().isEmpty()) {
return entry.getValue().get(0);
}
}
return null;
}

private static boolean isReplayed(Map<String, List<String>> headers) {
String value = headerValue(headers, "Idempotent-Replayed");
return value != null && value.trim().equalsIgnoreCase("true");
}

private static long retryDelayMillis(Map<String, List<String>> headers, int attempt, long baseDelayMillis) {
String retryAfter = headerValue(headers, "Retry-After");
if (retryAfter != null) {
try {
long seconds = Long.parseLong(retryAfter.trim());
if (seconds >= 0) {
return seconds * 1000L;
}
} catch (NumberFormatException ignored) {
// fall through to exponential backoff
}
}
return baseDelayMillis * (1L << attempt);
}

private static void sleep(long millis) throws ApiException {
if (millis <= 0) {
return;
}
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ApiException(e);
}
}
}
Loading