Repository: jchambers/pushy (java)

Issue:
Manifest issues
Hi.  I'm working on integrating pushy to replace javapns for HTTPS/2 support.  Our app runs in an OSGi environment, so the manifest is critical for us.  Two problems I've seen so far:

* fast-uuid-0.1.jar does not have an OSGi manifest, so cannot be imported from pushy-0.14.2.jar.  In its current form, it would need to be included as a private library, and removed from the manifest.
* pushy-0.14.2.jar's manifest both exports and imports the same packages.  This causes the bundle to not load - it should only (need to) export those packages.

Manually editing pushy-0.14.2.jar's manifest to remove those imports, and adding a proper manifest to fast-uuid-0.1.jar allows the bundles to load OK.

Here's the manifest I added/edited for fast-uuid (mostly copied from pushy-0.14.2.jar's):

`Manifest-Version: 1.0`
`Built-By: jon`
`Created-By: Apache Maven 3.3.9`
`Bundle-Description: A Java library for fast UUID stuff`
`Bundle-License: http://opensource.org/licenses/MIT`
`Bundle-SymbolicName: com.eatthepath.uuid`
`Built-By: jon`
`Bnd-LastModified: 1603298218405`
`Bundle-ManifestVersion: 2`
`Bundle-DocURL: https://github.com/jchambers`
`Bundle-Vendor: Jon Chambers`
`Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"`
`Tool: Bnd-4.2.0.201903051501`
`Export-Package: com.eatthepath.uuid`
`Bundle-Name: Fast UUID`
`Bundle-Version: 0.1`
`Build-Jdk: 14.0.1`

Pull request description:
Update to fast-uuid 0.2.0
This updates to fast-uuid 0.2.0. fast-uuid 0.2.0 has no functional changes, but is packaged as an OSGi bundle (thanks, @MushyMiddle!). Combined with #871, this closes #862.

Patch:
diff --git a/pom.xml b/pom.xml
index 46464eef..fca94ed1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,7 +110,7 @@
             <dependency>
                 <groupId>com.eatthepath</groupId>
                 <artifactId>fast-uuid</artifactId>
-                <version>0.1</version>
+                <version>0.2.0</version>
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java b/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
index b18da073..632fa678 100644
--- a/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
+++ b/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
@@ -74,6 +74,8 @@ public abstract class ApnsPayloadBuilder {
 
     private InterruptionLevel interruptionLevel = null;
 
+    private Double relevanceScore = null;
+
     private String[] urlArguments = null;
 
     private final HashMap<String, Object> customProperties = new HashMap<>();
@@ -93,6 +95,7 @@ public abstract class ApnsPayloadBuilder {
     private static final String SUMMARY_ARGUMENT_COUNT_KEY = "summary-arg-count";
     private static final String URL_ARGS_KEY = "url-args";
     private static final String INTERRUPTION_LEVEL_KEY = "interruption-level";
+    private static final String RELEVANCE_SCORE_KEY = "relevance-score";
 
     private static final String ALERT_TITLE_KEY = "title";
     private static final String ALERT_TITLE_LOC_KEY = "title-loc-key";
@@ -599,13 +602,38 @@ public abstract class ApnsPayloadBuilder {
      * @see <a href="https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/notifications/">Human
      * Interface Guidelines: Notifications</a>
      *
-     * @since 0.15
+     * @since 0.15.0
      */
     public ApnsPayloadBuilder setInterruptionLevel(final InterruptionLevel interruptionLevel) {
         this.interruptionLevel = interruptionLevel;
         return this;
     }
 
+    /**
+     * <p>Sets the relevance score for this notification. By default, no relevance score is included.</p>
+     *
+     * <p>The relevance score is:</p>
+     *
+     * <blockquote>…a number between 0 and 1, that the system uses to sort the notifications from your app. The highest
+     * score gets featured in the notification summary.</blockquote>
+     *
+     * @param relevanceScore a relevance score between 0 and 1, inclusive, or {@code null} if no relevance score should
+     * be included in the notification
+     *
+     * @return a reference to this payload builder
+     *
+     * @since 0.15.0
+     */
+    public ApnsPayloadBuilder setRelevanceScore(final Double relevanceScore) {
+        if (relevanceScore != null && (relevanceScore < 0 || relevanceScore > 1 || relevanceScore.isNaN())) {
+            throw new IllegalArgumentException("Relevance score must be a number between 0 and 1, inclusive, but was actually " + relevanceScore);
+        }
+
+        this.relevanceScore = relevanceScore;
+
+        return this;
+    }
+
     /**
      * <p>Sets the list of arguments to populate placeholders in the {@code urlFormatString} associated with a Safari
      * push notification. Has no effect for non-Safari notifications. According to the Notification Programming Guide
@@ -734,6 +762,10 @@ public abstract class ApnsPayloadBuilder {
                 aps.put(INTERRUPTION_LEVEL_KEY, this.interruptionLevel.getValue());
             }
 
+            if (this.relevanceScore != null) {
+                aps.put(RELEVANCE_SCORE_KEY, this.relevanceScore);
+            }
+
             final Map<String, Object> alert = new HashMap<>();
             {
                 if (this.alertBody != null) {


Test patch:
diff --git a/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java b/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
index 2ae99159..d1e0964c 100644
--- a/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
+++ b/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.text.ParseException;
 import java.util.ArrayList;
@@ -483,6 +484,32 @@ public abstract class ApnsPayloadBuilderTest {
         assertEquals("time-sensitive", aps.get("interruption-level"));
     }
 
+    @Test
+    void testSetRelevanceScore() {
+        {
+            final double relevanceScore = 0.7;
+            this.builder.setRelevanceScore(relevanceScore);
+
+            final Map<String, Object> aps = this.extractApsObjectFromPayloadString(this.builder.build());
+
+            assertEquals(relevanceScore, ((Number) aps.get("relevance-score")).doubleValue());
+        }
+
+        {
+            this.builder.setRelevanceScore(null);
+
+            final Map<String, Object> aps = this.extractApsObjectFromPayloadString(this.builder.build());
+
+            assertFalse(aps.containsKey("relevance-score"));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(doubles = {-7, 1.1, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY})
+    void testSetRelevanceScoreIllegalValue(final double illegalRelevanceScore) {
+        assertThrows(IllegalArgumentException.class, () -> this.builder.setRelevanceScore(illegalRelevanceScore));
+    }
+
     @Test
     void testSetSummaryArgumentCountNonPositive() {
         assertThrows(IllegalArgumentException.class, () -> this.builder.setSummaryArgumentCount(0));