diff --git a/README.adoc b/README.adoc index 5ddd877..f26bab8 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,11 @@ -= uProtocol Client Android Java Library += uProtocol Transport Android Java Library :toc: :toclevels: 4 :sectnums: :source-highlighter: coderay == Overview -The following is the uProtocol client library that implements uTransport, RpcClient and RpcService defined in https://github.com/eclipse-uprotocol/up-java[uProtocol Java Library] using Android Binder. It also includes some commonly used utilities for error handling. +The following is the uProtocol library that implements uTransport defined in https://github.com/eclipse-uprotocol/up-java[uProtocol Java Library] using Android Binder. It also includes some commonly used utilities for error handling. == Getting Started === Importing the Library @@ -15,13 +15,13 @@ If you are using Gradle, add the following to your _build.gradle_ file's depende ---- android { dependencies { - implementation 'org.eclipse.uprotocol:up-client-android-java::1.5.+' + implementation 'org.eclipse.uprotocol:up-transport-android-java::0.1.+' } } ---- === Configuring the Library -`UPClient`, by default, establishes a connection to uBus service that is integrated into the system as part of `"org.eclipse.uprotocol.core"` package. +`UTransprtAndroid`, by default, establishes a connection to uBus service that is integrated into the system as part of `"org.eclipse.uprotocol.core"` package. If a service that implements `IUBus.aidl` interface is integrated in a different package, you should configure the library by specifying that component or just that package. @@ -35,26 +35,25 @@ If a service that implements `IUBus.aidl` interface is integrated in a different === Using the Library ==== Connecting to uTransport -Before using the `UPClient` APIs, a uE must create an instance and connect to uBus. +Before using the `UTransportAndroid` APIs, a uE must create an instance and open connection to uBus. First create an instance with one of static factory methods: [,java] ---- -static UPClient create(Context context, Handler handler, ServiceLifecycleListener listener) -static UPClient create(Context context, Executor executor, ServiceLifecycleListener listener) -static UPClient create(Context context, UEntity entity, Handler handler, ServiceLifecycleListener listener) -static UPClient create(Context context, UEntity entity, Executor executor, ServiceLifecycleListener listener) +static UTransportAndroid create(Context context, Handler handler) +static UTransportAndroid create(Context context, Executor executor) +static UTransportAndroid create(Context context, UUri source, Handler handler) +static UTransportAndroid create(Context context, UUri source, Executor executor) ---- [%hardbreaks] `context` is an application context. -`entity` is information about uE containing its name and major version (MUST match the meta data in the manifest). +`source` is an address of uE containing its name and major version (MUST match the meta data in the manifest). `handler` is a handler on which callbacks should execute, or null to execute on the application's main thread. `executor` is an executor on which callbacks should execute, or null to execute on the application's main thread executor. -`listener` is a listener for monitoring uBus lifecycle. -NOTE: Every Android uE MUST declare its name and major version in the manifest. +NOTE: Every Android uE MUST declare its id and major version in the manifest. For the example below you may use any `create(...)` factory method. @@ -66,8 +65,8 @@ For the example below you may use any `create(...)` factory method. ... + android:name="uprotocol.entity.id" + android:value="100" /> @@ -79,7 +78,7 @@ For the example below you may use any `create(...)` factory method. ---- -For the next example you should create a separate instance of `UPClient` for each uE using `create(..., UEntity entity,...)` factory method. +For the next example you should create a separate instance of `UTransportAndroid` for each uE using `create(..., UUri source,...)` factory method. .Example 2: Several Android uEs bundled together in APK [,xml] @@ -92,8 +91,8 @@ For the next example you should create a separate instance of `UPClient` for eac android:name=".ExteriorLightingService" ... > + android:name="uprotocol.entity.id" + android:value="100" /> @@ -103,8 +102,8 @@ For the next example you should create a separate instance of `UPClient` for eac android:name=".InteriorLightingService" ... > + android:name="uprotocol.entity.id" + android:value="101" /> @@ -113,95 +112,51 @@ For the next example you should create a separate instance of `UPClient` for eac ---- -Then connect to uBus using a reactive API below: +Then open connection to uBus using a reactive API below: [,java] ---- -CompletionStage connect() +CompletionStage open() ---- -When you are done with the `UPClient` you should disconnect from uBus: +When you are done with the `UTransportAndroid` you should close the connection: [,java] ---- -CompletionStage disconnect() +void close() ---- -You cannot use other methods until the `UPClient` is connected. When this happens the `CompletionStage` returned by connect() will be completed and you also will receive the `onLifecycleChanged(..., true)` callback on your service lifecycle listener. You may query the connected status using these methods: +You cannot use other methods until the `UTransportAndroid` is connected. When this happens the `CompletionStage` returned by open() will be completed. You may query the connection status using this method: [,java] ---- -boolean isDisconnected() -boolean isConnecting() -boolean isConnected() +boolean isOpened() ---- ==== Sending a UMessage -For both, publisher/subscriber or observer (notification) design patterns, a uE should use the `UPClient` to send messages to consumers using any method below: +The method below is used to send messages to consumers: [,java] ---- -UStatus send(UUri source, UPayload payload, UAttributes attributes) -UStatus send(UMessage message) +CompletionStage send(UMessage message) ---- ==== Registering a UListener -In order to start receiving messages, a consumer should register a listener for a topic: +In order to start receiving messages, a consumer should register a listener: [,java] ---- -UStatus registerListener(UUri topic, UListener listener) +CompletionStage registerListener(UUri sourceFilter, UListener listener) +CompletionStage registerListener(UUri sourceFilter, UUri sinkFilter, UListener listener) ---- -*For the publisher/subscriber design pattern*, the precondition for a callback is that the uE needs to subscribe to the topic AND register the listener. +A consumer can use the same listener for multiple filters, or register different listeners with the same filters. -Given the precondition, the callback will be triggered in any of the following cases: - -. As soon as listener is registered if there is already a sent message for that topic that is in cache, OR -. Whenever the producer sends a new message for that topic - -*For the notification design pattern*, the only precondition is that uE needs to register the listener. -Once the listener is registered the callback will be triggered whenever the notification message is sent by the producer. - -A consumer can use the same listener for multiple topics, or register different listeners to the same topic. - -To unregister a listener from receiving topic messages: - -[,java] ----- -UStatus unregisterListener(UUri topic, UListener listener) ----- - -To unregister a listener from all topics: - -[,java] ----- -UStatus unregisterListener(UListener listener) ----- - -==== Registering a URpcListener -A uE with a service role should register a listener for a particular method URI to be notified when request messages are sent against said method. - -NOTE: Only one listener is allowed to be registered per a method URI. - -[,java] ----- -UStatus registerRpcListener(UUri methodUri, URpcListener listener) ----- - -To stop processing request messages for a specific method URI or all of the, a service uE should unregister the listener: - -[,java] ----- -UStatus unregisterRpcListener(UUri methodUri, URpcListener listener) -UStatus unregisterRpcListener(URpcListener listener) ----- - -==== Invoking an RPC Method -Code generators for uProtocol services defined in proto files primarily utilize the following method. However, clients also have the option to directly use it for invoking RPC methods. +To unregister a listener from receiving messages: [,java] ---- -CompletionStage invokeMethod(UUri methodUri, UPayload requestPayload, CallOptions options) +CompletionStage unregisterListener(UUri sourceFilter, UListener listener) +CompletionStage unregisterListener(UUri sourceFilter, UUri sinkFilter, UListener listener) ---- === Building the Library diff --git a/gradle/config.gradle b/gradle/config.gradle index 5335077..4d4fcbd 100644 --- a/gradle/config.gradle +++ b/gradle/config.gradle @@ -12,11 +12,17 @@ ext { config = [ toolsVersion : '34.0.0', - sdkVersion : 31, - minSdkVersion : 31, - namespace : 'org.eclipse.uprotocol.client', + sdkVersion : 34, + minSdkVersion : 34, + namespace : 'org.eclipse.uprotocol.transport', group : 'org.eclipse.uprotocol', - artifact : 'up-client-android-java', - version : '0.1.2', + artifact : 'up-transport-android-java', + version : '1.0.0', + jvm : [ + version : [ + java : JavaVersion.VERSION_17, + kotlin : "17", + ] + ], ] } diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index 2d4a1de..25cb934 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -23,12 +23,19 @@ tasks.register('jacocoTestReport', JacocoReport) { html.getRequired().set(true) } def buildDir = layout.buildDirectory.get() - executionData.from = ["${buildDir}/jacoco/testDebugUnitTest.exec"] - sourceDirectories.from = ["${projectDir}/src/main/java"] - classDirectories.from = fileTree( - dir: "${buildDir}/intermediates/javac/debug/classes/", - excludes: ['**/IU*', '**/BuildConfig.class'] - ) + def excludeClasses = [ + '**/BuildConfig.class', + '**/IU*', + ] + def javaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug/compileDebugJavaWithJavac/classes/", excludes: excludeClasses) + def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug/", excludes: excludeClasses) + getClassDirectories().setFrom(files([javaClasses, kotlinClasses])) + getSourceDirectories().setFrom(files([ + "${projectDir}/src/main/java", + "${buildDir}/generated/source/kapt/debug/", + ])) + getExecutionData().setFrom(files("${buildDir}/jacoco/testDebugUnitTest.exec")) + doLast { println "Unit-test coverage report generated at folder: ${buildDir}/reports/jacoco/jacocoTestReport/" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e15cd1..595c070 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,14 +10,15 @@ # SPDX-License-Identifier: Apache-2.0 # [versions] -android = "8.2.+" -androidx-appcompat = "1.4.2" -androidx-espresso = "3.5.1" -androidx-junit = "1.1.5" +android = "8.5.+" +androidx-appcompat = "1.7.0" +androidx-espresso = "3.6.1" +androidx-junit = "1.2.1" junit = "4.13.2" -mockito = "4.6.1" -robolectric = "4.7.3" -up-java = "0.1.9" +mockito = "5.5.0" +mockito-inline = "5.2.0" +robolectric = "4.12.1" +up-java = "2.0.0" [libraries] androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } @@ -26,7 +27,7 @@ androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-j junit = { module = "junit:junit", version.ref = "junit"} mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockito"} mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito"} -mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito"} +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito-inline"} robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric"} up-java = { module = "org.eclipse.uprotocol:up-java", version.ref = "up-java" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/library/build.gradle b/library/build.gradle index e7cf2d1..fc06f44 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -32,12 +32,23 @@ android { consumerProguardFiles 'consumer-rules.pro' } + signingConfigs { + platform { + storeFile file('certs/platform.keystore') + storePassword 'android' + keyAlias 'platform' + keyPassword 'android' + } + } + buildTypes { debug { + signingConfig signingConfigs.platform debuggable true minifyEnabled false } release { + signingConfig signingConfigs.platform minifyEnabled true proguardFiles 'proguard-rules.pro' } @@ -48,8 +59,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility config.jvm.version.java + targetCompatibility config.jvm.version.java } testNamespace "${config.namespace}.test" @@ -75,7 +86,6 @@ android { publishing { singleVariant('release') { - withJavadocJar() withSourcesJar() } } diff --git a/library/certs/platform.keystore b/library/certs/platform.keystore new file mode 100644 index 0000000..3743a8d Binary files /dev/null and b/library/certs/platform.keystore differ diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index 84b7242..f0bc020 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -18,11 +18,11 @@ -keep class * extends com.google.protobuf.** {*;} -keep class * extends com.google.protobuf.**$** {*;} --keep public class com.ultifi.core.** { +-keep public class org.eclipse.uprotocol.** { public protected *; (); } --keep public interface com.ultifi.core.** { +-keep public interface org.eclipse.uprotocol.** { *; } diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro index 750258b..8f2baa1 100644 --- a/library/proguard-rules.pro +++ b/library/proguard-rules.pro @@ -54,6 +54,9 @@ RuntimeVisibleTypeAnnotations, Signature +-keepclasseswithmembers class * { + @androidx.annotation.VisibleForTesting *; +} # The support libraries contains references to newer platform versions. # Don't warn about those in case this app is linking against an older diff --git a/library/src/androidTest/AndroidManifest.xml b/library/src/androidTest/AndroidManifest.xml index b2bb72d..ef0447b 100644 --- a/library/src/androidTest/AndroidManifest.xml +++ b/library/src/androidTest/AndroidManifest.xml @@ -14,11 +14,14 @@ + + + + android:name="uprotocol.entity.id" + android:value="0x50" /> diff --git a/library/src/androidTest/java/org/eclipse/uprotocol/TestBase.java b/library/src/androidTest/java/org/eclipse/uprotocol/TestBase.java index 2bfc321..1b8668e 100644 --- a/library/src/androidTest/java/org/eclipse/uprotocol/TestBase.java +++ b/library/src/androidTest/java/org/eclipse/uprotocol/TestBase.java @@ -23,123 +23,63 @@ */ package org.eclipse.uprotocol; +import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; +import static org.eclipse.uprotocol.communication.UPayload.packToAny; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import androidx.annotation.NonNull; import com.google.protobuf.Empty; -import org.eclipse.uprotocol.common.UStatusException; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.UAttributes; +import org.eclipse.uprotocol.communication.CallOptions; +import org.eclipse.uprotocol.communication.UPayload; +import org.eclipse.uprotocol.communication.UStatusException; import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UPayload; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UResource; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUri; -import java.util.concurrent.Future; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @SuppressWarnings("SameParameterValue") public class TestBase { - protected static final UEntity SERVICE = UEntity.newBuilder() - .setName("client.test") - .setVersionMajor(1) - .build(); - protected static final UEntity CLIENT = UEntity.newBuilder() - .setName("client.test") - .setVersionMajor(1) - .build(); - protected static final UResource RESOURCE = UResource.newBuilder() - .setName("resource") - .setInstance("main") - .setMessage("State") - .build(); - protected static final UResource RESOURCE2 = UResource.newBuilder() - .setName("resource2") - .setInstance("main2") - .setMessage("State2") - .build(); - protected static final UUri RESOURCE_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(RESOURCE) - .build(); - protected static final UUri RESOURCE2_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(RESOURCE2) - .build(); - protected static final UUri METHOD_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest("method")) + protected static final int VERSION = 1; + protected static final int CLIENT_ID = 0x50; + protected static final int SERVICE_ID = 0x50; + protected static final int METHOD_ID = 0x1; + protected static final int RESOURCE_ID = 0x8000; + protected static final UUri CLIENT_URI = UUri.newBuilder() + .setUeId(CLIENT_ID) + .setUeVersionMajor(VERSION) .build(); - protected static final UUri METHOD2_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest("method2")) + protected static final UUri SERVICE_URI = UUri.newBuilder() + .setUeId(SERVICE_ID) + .setUeVersionMajor(VERSION) .build(); - protected static final UUri RESPONSE_URI = UUri.newBuilder() - .setEntity(CLIENT) - .setResource(UResourceBuilder.forRpcResponse()) + protected static final UUri METHOD_URI = UUri.newBuilder(SERVICE_URI) + .setResourceId(METHOD_ID) .build(); - protected static final UUri CLIENT_URI = UUri.newBuilder() - .setEntity(CLIENT) + protected static final UUri RESOURCE_URI = UUri.newBuilder(SERVICE_URI) + .setResourceId(RESOURCE_ID) .build(); protected static final UPayload PAYLOAD = packToAny(Empty.getDefaultInstance()); - protected static final int TTL = 5000; + protected static final UPayload PAYLOAD2 = packToAny(STATUS_OK); + protected static final int TTL = CallOptions.DEFAULT.timeout(); protected static final long CONNECTION_TIMEOUT_MS = 3000; protected static final long DELAY_MS = 100; - protected static @NonNull UAttributes buildPublishAttributes(@NonNull UUri source) { - return newPublishAttributesBuilder(source).build(); - } - - protected static @NonNull UAttributesBuilder newPublishAttributesBuilder(@NonNull UUri source) { - return UAttributesBuilder.publish(source, UPriority.UPRIORITY_CS0); - } - - protected static @NonNull UAttributesBuilder newNotificationAttributesBuilder(@NonNull UUri source, @NonNull UUri sink) { - return UAttributesBuilder.notification(source, sink, UPriority.UPRIORITY_CS0); - } - - protected static @NonNull UMessage buildMessage(UPayload payload, UAttributes attributes) { - final UMessage.Builder builder = UMessage.newBuilder(); - if (payload != null) { - builder.setPayload(payload); - } - if (attributes != null) { - builder.setAttributes(attributes); - } - return builder.build(); - } - - protected static void connect(@NonNull UPClient client) { - assertStatus(UCode.OK, getOrThrow(client.connect().toCompletableFuture(), CONNECTION_TIMEOUT_MS)); - assertTrue(client.isConnected()); - } - - protected static void disconnect(@NonNull UPClient client) { - assertStatus(UCode.OK, getOrThrow(client.disconnect().toCompletableFuture())); - assertTrue(client.isDisconnected()); - } - protected static void assertStatus(@NonNull UCode code, @NonNull UStatus status) { assertEquals(code, status.getCode()); } - protected static T getOrThrow(@NonNull Future future) { - return getOrThrow(future, DELAY_MS); + protected static T getOrThrow(@NonNull CompletionStage stage) { + return getOrThrow(stage, DELAY_MS); } - protected static T getOrThrow(@NonNull Future future, long timeout) { + protected static T getOrThrow(@NonNull CompletionStage stage, long timeout) { try { - return future.get(timeout, TimeUnit.MILLISECONDS); + return stage.toCompletableFuture().get(timeout, TimeUnit.MILLISECONDS); } catch (Exception e) { throw new UStatusException(toStatus(e)); } diff --git a/library/src/androidTest/java/org/eclipse/uprotocol/UPClientTest.java b/library/src/androidTest/java/org/eclipse/uprotocol/UPClientTest.java deleted file mode 100644 index 5bd0135..0000000 --- a/library/src/androidTest/java/org/eclipse/uprotocol/UPClientTest.java +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol; - -import static junit.framework.TestCase.assertEquals; - -import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; -import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.google.protobuf.Int32Value; - -import org.eclipse.uprotocol.UPClient.ServiceLifecycleListener; -import org.eclipse.uprotocol.core.usubscription.v3.CreateTopicRequest; -import org.eclipse.uprotocol.core.usubscription.v3.SubscriberInfo; -import org.eclipse.uprotocol.core.usubscription.v3.SubscriptionRequest; -import org.eclipse.uprotocol.core.usubscription.v3.SubscriptionResponse; -import org.eclipse.uprotocol.core.usubscription.v3.USubscription; -import org.eclipse.uprotocol.core.usubscription.v3.UnsubscribeRequest; -import org.eclipse.uprotocol.transport.UListener; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UMessageType; -import org.eclipse.uprotocol.v1.UPayload; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -@RunWith(AndroidJUnit4.class) -public class UPClientTest extends TestBase { - private static final UMessage MESSAGE = buildMessage(PAYLOAD, buildPublishAttributes(RESOURCE_URI)); - private static final UMessage NOTIFICATION_MESSAGE = buildMessage(PAYLOAD, - newNotificationAttributesBuilder(RESOURCE_URI, CLIENT_URI).build()); - private static final CallOptions OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(TTL) - .build(); - private static final UPayload REQUEST_PAYLOAD = packToAny(Int32Value.newBuilder().setValue(1).build()); - private static final UPayload RESPONSE_PAYLOAD = packToAny(STATUS_OK); - - private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); - private static final ServiceLifecycleListener sServiceLifecycleListener = mock(ServiceLifecycleListener.class); - private static final UListener sListener = mock(UListener.class); - private static final UListener sListener2 = mock(UListener.class); - private static Context sContext; - private static UPClient sClient; - private static USubscription.Stub sSubscriptionStub; - - @BeforeClass - public static void setUp() { - sContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - sClient = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - sSubscriptionStub = USubscription.newStub(sClient, OPTIONS); - connect(sClient); - } - - @AfterClass - public static void tearDown() { - unsubscribe(RESOURCE_URI); - disconnect(sClient); - sExecutor.shutdown(); - } - - @After - public void tearDownTest() { - reset(sServiceLifecycleListener); - reset(sListener); - reset(sListener2); - sClient.unregisterListener(sListener); - sClient.unregisterListener(sListener2); - } - - private static void createTopic(@NonNull UUri topic) { - CompletableFuture future = sSubscriptionStub.createTopic(CreateTopicRequest.newBuilder() - .setTopic(topic) - .build()).toCompletableFuture(); - assertStatus(UCode.OK, getOrThrow(future, OPTIONS.getTtl())); - } - - private static void subscribe(@NonNull UUri topic) { - CompletableFuture future = sSubscriptionStub.subscribe(SubscriptionRequest.newBuilder() - .setTopic(topic) - .setSubscriber(SubscriberInfo.newBuilder(). - setUri(UUri.newBuilder() - .setEntity(sClient.getEntity()) - .build())) - .build()).toCompletableFuture(); - assertEquals(UCode.OK, getOrThrow(future, OPTIONS.getTtl()).getStatus().getCode()); - } - - private static void unsubscribe(@NonNull UUri topic) { - CompletableFuture future = sSubscriptionStub.unsubscribe(UnsubscribeRequest.newBuilder() - .setTopic(topic) - .setSubscriber(SubscriberInfo.newBuilder(). - setUri(UUri.newBuilder() - .setEntity(sClient.getEntity()) - .build())) - .build()).toCompletableFuture(); - assertStatus(UCode.OK, getOrThrow(future, OPTIONS.getTtl())); - } - - @Test - public void testConnect() { - final UPClient client = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - connect(client); - verify(sServiceLifecycleListener, timeout(DELAY_MS).times(1)).onLifecycleChanged(client, true); - client.disconnect(); - } - - @Test - public void testConnectDuplicated() { - final UPClient client = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - final CompletableFuture future1 = client.connect().toCompletableFuture(); - final CompletableFuture future2 = client.connect().toCompletableFuture(); - assertStatus(UCode.OK, getOrThrow(future1, CONNECTION_TIMEOUT_MS)); - assertStatus(UCode.OK, getOrThrow(future2, CONNECTION_TIMEOUT_MS)); - verify(sServiceLifecycleListener, timeout(DELAY_MS).times(1)).onLifecycleChanged(client, true); - assertTrue(client.isConnected()); - client.disconnect(); - } - - @Test - public void testDisconnect() { - final UPClient client = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - connect(client); - assertStatus(UCode.OK, getOrThrow(client.disconnect().toCompletableFuture(), DELAY_MS)); - verify(sServiceLifecycleListener, timeout(DELAY_MS).times(1)).onLifecycleChanged(client, false); - assertTrue(client.isDisconnected()); - } - - @Test - public void testDisconnectNotConnected() { - final UPClient client = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - assertStatus(UCode.OK, getOrThrow(client.disconnect().toCompletableFuture(), DELAY_MS)); - verify(sServiceLifecycleListener, timeout(DELAY_MS).times(0)).onLifecycleChanged(client, false); - assertTrue(client.isDisconnected()); - } - - @Test - public void testDisconnectWhileConnecting() { - final UPClient client = UPClient.create(sContext, sExecutor, sServiceLifecycleListener); - final CompletableFuture future = client.connect().toCompletableFuture(); - assertStatus(UCode.OK, getOrThrow(client.disconnect().toCompletableFuture(), DELAY_MS)); - assertTrue(Set.of(UCode.OK, UCode.CANCELLED) - .contains(getOrThrow(future, CONNECTION_TIMEOUT_MS).getCode())); - assertTrue(client.isDisconnected()); - } - - @Test - public void testGetEntity() { - assertEquals(CLIENT, sClient.getEntity()); - } - - @Test - public void testSubscription() { - createTopic(RESOURCE2_URI); - subscribe(RESOURCE2_URI); - unsubscribe(RESOURCE2_URI); - } - - @Test - public void testSend() { - createTopic(RESOURCE_URI); - assertStatus(UCode.OK, sClient.send(MESSAGE)); - } - - @Test - public void testSendNotificationMessage() { - assertStatus(UCode.OK, sClient.send(NOTIFICATION_MESSAGE)); - } - - @Test - public void testRegisterGenericListener() { - assertStatus(UCode.OK, sClient.registerListener(RESOURCE_URI, sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testRegisterGenericListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.registerListener(UUri.getDefaultInstance(), sListener)); - assertStatus(UCode.INVALID_ARGUMENT, sClient.registerListener(RESOURCE_URI, null)); - } - - @Test - public void testRegisterGenericListenerDifferentTopics() { - testRegisterGenericListener(); - assertStatus(UCode.OK, sClient.registerListener(RESOURCE2_URI, sListener)); - } - - @Test - public void testRegisterGenericListenerSame() { - testRegisterGenericListener(); - assertStatus(UCode.OK, sClient.registerListener(RESOURCE_URI, sListener)); - } - - @Test - public void testRegisterGenericListenerNotFirst() { - testRegisterGenericListener(); - assertStatus(UCode.OK, sClient.registerListener(RESOURCE_URI, sListener2)); - } - - @Test - public void testUnregisterGenericListener() { - testRegisterGenericListener(); - assertStatus(UCode.OK, sClient.unregisterListener(RESOURCE_URI, sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterGenericListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(UUri.getDefaultInstance(), sListener)); - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(RESOURCE_URI, null)); - } - - @Test - public void testUnregisterGenericListenerSame() { - testUnregisterGenericListener(); - assertStatus(UCode.OK, sClient.unregisterListener(RESOURCE_URI, sListener)); - } - - @Test - public void testUnregisterGenericListenerNotRegistered() { - testRegisterGenericListener(); - assertStatus(UCode.OK, sClient.unregisterListener(RESOURCE_URI, sListener2)); - } - - @Test - public void testUnregisterGenericListenerNotLast() { - testRegisterGenericListenerNotFirst(); - assertStatus(UCode.OK, sClient.unregisterListener(RESOURCE_URI, sListener)); - } - - @Test - public void testUnregisterGenericListenerLast() { - testUnregisterGenericListenerNotLast(); - assertStatus(UCode.OK, sClient.unregisterListener(RESOURCE_URI, sListener2)); - } - - @Test - public void testUnregisterGenericListenerFromAllTopics() { - testRegisterGenericListenerDifferentTopics(); - assertStatus(UCode.OK, sClient.unregisterListener(sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterGenericListenerFromAllTopicsWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(null)); - } - - @Test - public void testOnReceiveGenericMessage() { - testSend(); - subscribe(RESOURCE_URI); - testRegisterGenericListenerNotFirst(); - verify(sListener, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); - verify(sListener2, timeout(DELAY_MS).atLeastOnce()).onReceive(MESSAGE); - } - - @Test - public void testOnReceiveGenericMessageNotRegistered() { - testSend(); - subscribe(RESOURCE_URI); - testRegisterGenericListener(); - verify(sListener, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); - verify(sListener2, timeout(DELAY_MS).times(0)).onReceive(MESSAGE); - } - - @Test - public void testOnReceiveNotificationMessage() { - testRegisterGenericListener(); - testSendNotificationMessage(); - verify(sListener, timeout(DELAY_MS).times(1)).onReceive(NOTIFICATION_MESSAGE); - } - - @Test - public void testRegisterRequestListener() { - assertEquals(STATUS_OK, sClient.registerListener(METHOD_URI, sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testRegisterRequestListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.registerListener(UUri.getDefaultInstance(), sListener)); - assertStatus(UCode.INVALID_ARGUMENT, sClient.registerListener(METHOD_URI, null)); - } - - @Test - public void testRegisterRequestListenerDifferentMethods() { - assertStatus(UCode.OK, sClient.registerListener(METHOD_URI, sListener)); - assertStatus(UCode.OK, sClient.registerListener(METHOD2_URI, sListener)); - } - - @Test - public void testRegisterRequestListenerSame() { - testRegisterRequestListener(); - assertStatus(UCode.OK, sClient.registerListener(METHOD_URI, sListener)); - } - - @Test - public void testRegisterRequestListenerNotFirst() { - testRegisterRequestListener(); - assertStatus(UCode.ALREADY_EXISTS, sClient.registerListener(METHOD_URI, sListener2)); - } - - @Test - public void testUnregisterRequestListener() { - testRegisterRequestListener(); - assertStatus(UCode.OK, sClient.unregisterListener(METHOD_URI, sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterRequestListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(UUri.getDefaultInstance(), sListener)); - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(METHOD_URI, null)); - } - - @Test - public void testUnregisterRequestListenerSame() { - testUnregisterRequestListener(); - assertStatus(UCode.OK, sClient.unregisterListener(METHOD_URI, sListener)); - } - - @Test - public void testUnregisterRequestListenerNotRegistered() { - testRegisterRequestListener(); - assertStatus(UCode.OK, sClient.unregisterListener(METHOD_URI, sListener2)); - } - - @Test - public void testUnregisterRequestListenerFromAllMethods() { - testRegisterRequestListenerDifferentMethods(); - assertStatus(UCode.OK, sClient.unregisterListener(sListener)); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterRequestListenerFromAllMethodsWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, sClient.unregisterListener(null)); - } - - @Test - public void testUnregisterRequestListenerFromAllMethodsNotRegistered() { - testRegisterRequestListenerDifferentMethods(); - assertStatus(UCode.OK, sClient.unregisterListener(sListener2)); - } - - @Test - public void testInvokeMethod() throws Exception { - testRegisterRequestListener(); - - final CompletableFuture responseFuture = - sClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(sListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - final UAttributes requestAttributes = requestMessage.getAttributes(); - assertEquals(RESPONSE_URI.getEntity(), requestAttributes.getSource().getEntity()); - assertEquals(REQUEST_PAYLOAD, requestMessage.getPayload()); - assertEquals(METHOD_URI, requestAttributes.getSink()); - assertEquals(OPTIONS.getPriority(), requestAttributes.getPriority()); - assertEquals(OPTIONS.getTtl(), requestAttributes.getTtl()); - assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, requestAttributes.getType()); - sClient.send(UMessage.newBuilder() - .setPayload(RESPONSE_PAYLOAD) - .setAttributes(UAttributesBuilder.response(requestAttributes).build()) - .build()); - - final UMessage responseMessage = responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS); - final UAttributes responseAttributes = responseMessage.getAttributes();; - assertEquals(METHOD_URI, responseAttributes.getSource()); - assertEquals(RESPONSE_PAYLOAD, responseMessage.getPayload()); - assertEquals(RESPONSE_URI, responseAttributes.getSink()); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, responseAttributes.getType()); - assertEquals(requestAttributes.getId(), responseAttributes.getReqid()); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testInvokeMethodWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> sClient.invokeMethod(null, PAYLOAD, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> sClient.invokeMethod(UUri.getDefaultInstance(), PAYLOAD, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> sClient.invokeMethod(METHOD_URI, null, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> sClient.invokeMethod(METHOD_URI, PAYLOAD, null).toCompletableFuture().get()))); - } - - @Test - public void testInvokeMethodCompletedWithCommStatus() { - testRegisterRequestListener(); - - final CompletableFuture responseFuture = - sClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(sListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - final UAttributes requestAttributes = requestMessage.getAttributes(); - sClient.send(UMessage.newBuilder() - .setAttributes(UAttributesBuilder - .response(requestAttributes) - .withCommStatus(UCode.CANCELLED) - .build()) - .build()); - - assertStatus(UCode.CANCELLED, toStatus(assertThrows( - ExecutionException.class, () -> responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS)))); - } - - @Test - public void testInvokeMethodNoServer() { - assertStatus(UCode.UNAVAILABLE, toStatus(assertThrows(ExecutionException.class, - () -> sClient.invokeMethod(METHOD_URI, PAYLOAD, OPTIONS).toCompletableFuture().get()))); - } -} diff --git a/library/src/androidTest/java/org/eclipse/uprotocol/UTransportAndroidTest.java b/library/src/androidTest/java/org/eclipse/uprotocol/UTransportAndroidTest.java new file mode 100644 index 0000000..b9a2c83 --- /dev/null +++ b/library/src/androidTest/java/org/eclipse/uprotocol/UTransportAndroidTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2024 General Motors GTO LLC + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * SPDX-FileType: SOURCE + * SPDX-FileCopyrightText: 2023 General Motors GTO LLC + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.eclipse.uprotocol.client.usubscription.v3.InMemoryUSubscriptionClient; +import org.eclipse.uprotocol.client.usubscription.v3.USubscriptionClient; +import org.eclipse.uprotocol.communication.CallOptions; +import org.eclipse.uprotocol.communication.RequestHandler; +import org.eclipse.uprotocol.communication.UClient; +import org.eclipse.uprotocol.core.usubscription.v3.SubscriptionStatus.State; +import org.eclipse.uprotocol.transport.UListener; +import org.eclipse.uprotocol.transport.UTransportAndroid; +import org.eclipse.uprotocol.v1.UCode; +import org.eclipse.uprotocol.v1.UStatus; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@RunWith(AndroidJUnit4.class) +public class UTransportAndroidTest extends TestBase { + private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); + private static final UListener sListener = mock(UListener.class); + private static final RequestHandler sRequestHandler = mock(RequestHandler.class); + private static Context sContext; + private static UTransportAndroid sTransport; + private static UClient sClient; + private static USubscriptionClient sSubscriptionClient; + + @BeforeClass + public static void setUp() { + sContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + sTransport = UTransportAndroid.create(sContext, sExecutor); + assertStatus(UCode.OK, getOrThrow(sTransport.open(), CONNECTION_TIMEOUT_MS)); + sClient = UClient.create(sTransport); + sSubscriptionClient = new InMemoryUSubscriptionClient(sTransport); + } + + @AfterClass + public static void tearDown() { + sTransport.close(); + sExecutor.shutdown(); + } + + @After + public void tearDownTest() { + reset(sListener); + reset(sRequestHandler); + } + + @Test + public void testOpen() { + final UTransportAndroid transport = UTransportAndroid.create(sContext, sExecutor); + assertStatus(UCode.OK, getOrThrow(transport.open(), CONNECTION_TIMEOUT_MS)); + assertTrue(transport.isOpened()); + transport.close(); + } + + @Test + public void testOpenDuplicated() { + final UTransportAndroid transport = UTransportAndroid.create(sContext, sExecutor); + final CompletionStage stage1 = transport.open(); + final CompletionStage stage2 = transport.open(); + assertStatus(UCode.OK, getOrThrow(stage1, CONNECTION_TIMEOUT_MS)); + assertStatus(UCode.OK, getOrThrow(stage2, CONNECTION_TIMEOUT_MS)); + assertTrue(transport.isOpened()); + transport.close(); + } + + @Test + public void testClose() { + final UTransportAndroid transport = UTransportAndroid.create(sContext, sExecutor); + assertStatus(UCode.OK, getOrThrow(transport.open(), CONNECTION_TIMEOUT_MS)); + transport.close(); + assertFalse(transport.isOpened()); + } + + @Test + public void testCloseNotOpened() { + final UTransportAndroid transport = UTransportAndroid.create(sContext, sExecutor); + transport.close(); + assertFalse(transport.isOpened()); + } + + @Test + public void testCloseWhileOpening() { + final UTransportAndroid transport = UTransportAndroid.create(sContext, sExecutor); + final CompletionStage stage = transport.open(); + transport.close(); + assertTrue(Set.of(UCode.OK, UCode.CANCELLED).contains(getOrThrow(stage, CONNECTION_TIMEOUT_MS).getCode())); + assertFalse(transport.isOpened()); + } + + @Test + public void testGetSource() { + assertEquals(CLIENT_URI, sTransport.getSource()); + } + + @Test + public void testSendPublishMessage() { + assertStatus(UCode.OK, getOrThrow(sClient.publish(RESOURCE_URI, PAYLOAD))); + } + + @Test + public void testSendNotificationMessage() { + assertStatus(UCode.OK, getOrThrow(sClient.notify(RESOURCE_URI, CLIENT_URI, PAYLOAD))); + } + + @Test + public void testOnReceivePublishMessage() { + assertEquals(State.SUBSCRIBED, + getOrThrow(sSubscriptionClient.subscribe(RESOURCE_URI, sListener), TTL).getStatus().getState()); + testSendPublishMessage(); + verify(sListener, timeout(DELAY_MS).times(1)).onReceive(argThat(message -> { + assertEquals(RESOURCE_URI, message.getAttributes().getSource()); + assertEquals(PAYLOAD.data(), message.getPayload()); + return true; + })); + assertStatus(UCode.OK, getOrThrow(sSubscriptionClient.unsubscribe(RESOURCE_URI, sListener), TTL)); + } + + @Test + public void testOnReceiveNotificationMessage() { + assertStatus(UCode.OK, getOrThrow(sClient.registerNotificationListener(RESOURCE_URI, sListener))); + testSendNotificationMessage(); + verify(sListener, timeout(DELAY_MS).times(1)).onReceive(argThat(message -> { + assertEquals(RESOURCE_URI, message.getAttributes().getSource()); + assertEquals(CLIENT_URI, message.getAttributes().getSink()); + assertEquals(PAYLOAD.data(), message.getPayload()); + return true; + })); + assertStatus(UCode.OK, getOrThrow(sClient.unregisterNotificationListener(RESOURCE_URI, sListener))); + } + + @Test + public void testOnReceiveRpcMessages() { + sClient.registerRequestHandler(METHOD_URI, sRequestHandler); + doReturn(PAYLOAD2).when(sRequestHandler).handleRequest(argThat(message -> { + assertEquals(CLIENT_URI, message.getAttributes().getSource()); + assertEquals(METHOD_URI, message.getAttributes().getSink()); + assertEquals(PAYLOAD.data(), message.getPayload()); + return true; + })); + assertEquals(PAYLOAD2, getOrThrow(sClient.invokeMethod(METHOD_URI, PAYLOAD, CallOptions.DEFAULT), TTL)); + sClient.unregisterRequestHandler(METHOD_URI, sRequestHandler); + } +} diff --git a/library/src/main/aidl/org/eclipse/uprotocol/core/ubus/IUBus.aidl b/library/src/main/aidl/org/eclipse/uprotocol/core/ubus/IUBus.aidl index 4402b3d..baefc4f 100644 --- a/library/src/main/aidl/org/eclipse/uprotocol/core/ubus/IUBus.aidl +++ b/library/src/main/aidl/org/eclipse/uprotocol/core/ubus/IUBus.aidl @@ -24,16 +24,14 @@ package org.eclipse.uprotocol.core.ubus; import org.eclipse.uprotocol.core.ubus.IUListener; -import org.eclipse.uprotocol.v1.internal.ParcelableUEntity; import org.eclipse.uprotocol.v1.internal.ParcelableUMessage; import org.eclipse.uprotocol.v1.internal.ParcelableUStatus; import org.eclipse.uprotocol.v1.internal.ParcelableUUri; interface IUBus { - ParcelableUStatus registerClient(in String packageName, in ParcelableUEntity entity, in IBinder clientToken, in int flags, in IUListener listener); + ParcelableUStatus registerClient(in String packageName, in ParcelableUUri clientUri, in IBinder clientToken, in int flags, in IUListener listener); ParcelableUStatus unregisterClient(in IBinder clientToken); ParcelableUStatus send(in ParcelableUMessage message, in IBinder clientToken); - @nullable ParcelableUMessage[] pull(in ParcelableUUri uri, int count, in int flags, IBinder clientToken); - ParcelableUStatus enableDispatching(in ParcelableUUri uri, in int flags, IBinder clientToken); - ParcelableUStatus disableDispatching(in ParcelableUUri uri, in int flags, IBinder clientToken); + ParcelableUStatus enableDispatching(in ParcelableUUri sourceFilter, in ParcelableUUri sinkFilter, in int flags, IBinder clientToken); + ParcelableUStatus disableDispatching(in ParcelableUUri sourceFilter, in ParcelableUUri sinkFilter, in int flags, IBinder clientToken); } diff --git a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUAuthority.aidl b/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUAuthority.aidl deleted file mode 100644 index f1b9e9f..0000000 --- a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUAuthority.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.v1.internal; - -parcelable ParcelableUAuthority; diff --git a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.aidl b/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.aidl deleted file mode 100644 index a857055..0000000 --- a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.v1.internal; - -parcelable ParcelableUEntity; diff --git a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUResource.aidl b/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUResource.aidl deleted file mode 100644 index 7cc67c2..0000000 --- a/library/src/main/aidl/org/eclipse/uprotocol/v1/internal/ParcelableUResource.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.v1.internal; - -parcelable ParcelableUResource; diff --git a/library/src/main/java/org/eclipse/uprotocol/UPClient.java b/library/src/main/java/org/eclipse/uprotocol/UPClient.java deleted file mode 100644 index b917cb0..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/UPClient.java +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol; - -import static com.google.common.base.Strings.isNullOrEmpty; - -import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; -import static org.eclipse.uprotocol.common.util.UStatusUtils.checkArgument; -import static org.eclipse.uprotocol.common.util.UStatusUtils.checkArgumentPositive; -import static org.eclipse.uprotocol.common.util.UStatusUtils.checkNotNull; -import static org.eclipse.uprotocol.common.util.UStatusUtils.isOk; -import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; -import static org.eclipse.uprotocol.common.util.log.Formatter.join; -import static org.eclipse.uprotocol.common.util.log.Formatter.stringify; -import static org.eclipse.uprotocol.common.util.log.Formatter.tag; -import static org.eclipse.uprotocol.transport.validate.UAttributesValidator.getValidator; -import static org.eclipse.uprotocol.uri.validator.UriValidator.isEmpty; -import static org.eclipse.uprotocol.uri.validator.UriValidator.isRpcMethod; - -import static java.util.Optional.ofNullable; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Handler; -import android.util.ArraySet; -import android.util.Log; - -import androidx.annotation.GuardedBy; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import org.eclipse.uprotocol.client.BuildConfig; -import org.eclipse.uprotocol.common.UStatusException; -import org.eclipse.uprotocol.common.util.log.Key; -import org.eclipse.uprotocol.core.ubus.ConnectionCallback; -import org.eclipse.uprotocol.core.ubus.UBusManager; -import org.eclipse.uprotocol.internal.HandlerExecutor; -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.transport.UListener; -import org.eclipse.uprotocol.transport.UTransport; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UPayload; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUID; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.validation.ValidationResult; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -/** - * The uProtocol client API layer which enables communication over the uBus, - * offering basic functionalities for establishing connections, sending and - * receiving messages, and invoking RPC methods. - */ -@SuppressWarnings({"java:S1192", "java:S3398", "java:S6539"}) -public final class UPClient implements UTransport, RpcClient { - /** - * The logging group tag used by this class and all sub-components. - */ - public static final String TAG_GROUP = "uPClient"; - - /** - * The permission necessary to connect to the uBus. - */ - public static final String PERMISSION_ACCESS_UBUS = "uprotocol.permission.ACCESS_UBUS"; - - /** - * The name of the meta-data element that must be present on an - * application or service element in a manifest to specify - * the name of a client (uEntity). - */ - public static final String META_DATA_ENTITY_NAME = "uprotocol.entity.name"; - - /** - * The name of the meta-data element that must be present on an - * application or service element in a manifest to specify - * the major version of a client (uEntity). - */ - public static final String META_DATA_ENTITY_VERSION = "uprotocol.entity.version"; - - /** - * The name of the meta-data element that may be present on an - * application or service element in a manifest to specify - * the id of a client (uEntity). - */ - public static final String META_DATA_ENTITY_ID = "uprotocol.entity.id"; - - private static final String MESSAGE_RECEIVED = "Message received"; - private static final String MESSAGE_DROPPED = "Message dropped"; - - private final UUri mUri; - private final UUri mResponseUri; - private final UBusManager mUBusManager; - private final Executor mCallbackExecutor; - private final ServiceLifecycleListener mServiceLifecycleListener; - - private final ConcurrentHashMap> mRequests = new ConcurrentHashMap<>(); - private final Object mRegistrationLock = new Object(); - @GuardedBy("mRegistrationLock") - private final Map> mGenericListeners = new HashMap<>(); - @GuardedBy("mRegistrationLock") - private final Map mRequestListeners = new HashMap<>(); - @GuardedBy("mRegistrationLock") - private boolean mRegistrationExpired; - - private final String mTag; - private boolean mVerboseLoggable; - - private final ConnectionCallback mConnectionCallback = new ConnectionCallback() { - @Override - public void onConnected() { - mCallbackExecutor.execute(() -> { - renewRegistration(); - mServiceLifecycleListener.onLifecycleChanged(UPClient.this, true); - }); - } - - @Override - public void onDisconnected() { - mCallbackExecutor.execute(() -> { - release(); - mServiceLifecycleListener.onLifecycleChanged(UPClient.this, false); - }); - } - - @Override - public void onConnectionInterrupted() { - mCallbackExecutor.execute(() -> { - setRegistrationExpired(); - mServiceLifecycleListener.onLifecycleChanged(UPClient.this, false); - }); - } - }; - - private final UListener mListener = this::handleMessage; - - /** - * The callback to notify the lifecycle of the uBus. - * - *

Access to the uBus should happen - * after {@link ServiceLifecycleListener#onLifecycleChanged(UPClient, boolean)} call with - * ready set true. - */ - public interface ServiceLifecycleListener { - /** - * The uBus has gone through status change. - * - * @param client A {@link UPClient} object that was originally associated with this - * listener from {@link #create(Context, Handler, ServiceLifecycleListener)} call. - * @param ready When true, the uBus is ready and all accesses are ok. - * Otherwise it has crashed or killed and will be restarted. - */ - void onLifecycleChanged(@NonNull UPClient client, boolean ready); - } - - @VisibleForTesting - UPClient(@NonNull Context context, @Nullable UEntity entity, @Nullable UBusManager manager, - @Nullable Executor executor, @Nullable ServiceLifecycleListener listener) { - checkNonNullContext(context); - entity = checkContainsEntity(getPackageInfo(context), entity); - mUri = UUri.newBuilder() - .setEntity(entity) - .build(); - mResponseUri = UUri.newBuilder(mUri) - .setResource(UResourceBuilder.forRpcResponse()) - .build(); - mUBusManager = ofNullable(manager).orElse(new UBusManager(context, entity, mConnectionCallback, mListener)); - mCallbackExecutor = ofNullable(executor).orElse(context.getMainExecutor()); - mServiceLifecycleListener = ofNullable(listener).orElse((client, ready) -> {}); - - mTag = tag(entity.getName(), TAG_GROUP); - mVerboseLoggable = Log.isLoggable(mTag, Log.VERBOSE); - if (mVerboseLoggable) { - Log.v(mTag, join(Key.PACKAGE, BuildConfig.LIBRARY_PACKAGE_NAME, Key.VERSION, BuildConfig.VERSION_NAME)); - } - } - - private static void checkNonNullContext(Context context) { - checkNotNull(context, "Context is null"); - if (context instanceof ContextWrapper contextWrapper && contextWrapper.getBaseContext() == null) { - throw new NullPointerException("ContextWrapper with null base passed as Context"); - } - } - - private static @NonNull PackageInfo getPackageInfo(@NonNull Context context) { - try { - return context.getPackageManager().getPackageInfo(context.getPackageName(), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); - } catch (NameNotFoundException e) { - throw new SecurityException(e.getMessage(), e); - } - } - - private static @NonNull UEntity checkContainsEntity(@NonNull PackageInfo packageInfo, @Nullable UEntity entity) { - return Stream.concat(Stream.of(packageInfo.applicationInfo), - (packageInfo.services != null) ? Stream.of(packageInfo.services) : Stream.empty()) - .filter(Objects::nonNull) - .map(info -> { - final UEntity foundEntity = getEntity(info); - if (entity != null) { - return entity.equals(foundEntity) ? entity : null; - } else { - return foundEntity; - } - }) - .filter(Objects::nonNull) - .findFirst() - .orElseThrow(() -> new SecurityException("Missing or not matching '" + META_DATA_ENTITY_NAME + "', '" + - META_DATA_ENTITY_VERSION + "' or '" + META_DATA_ENTITY_ID + "' meta-data in manifest")); - } - - private static @Nullable UEntity getEntity(@NonNull PackageItemInfo info) { - if (info.metaData != null) { - final String name = info.metaData.getString(META_DATA_ENTITY_NAME); - final int version = info.metaData.getInt(META_DATA_ENTITY_VERSION); - final int id = info.metaData.getInt(META_DATA_ENTITY_ID); - if (!isNullOrEmpty(name) && version > 0) { - final UEntity.Builder builder = UEntity.newBuilder() - .setName(name) - .setVersionMajor(version); - if (id > 0) { - builder.setId(id); - } - return builder.build(); - } - } - return null; - } - - /** - * Create an instance. - * - * @param context An application {@link Context}. This should not be null. If you are passing - * {@link ContextWrapper}, make sure that its base Context is non-null as well. - * Otherwise it will throw {@link NullPointerException}. - * @param handler A {@link Handler} on which callbacks should execute, or null to execute on - * the application's main thread. - * @param listener A {@link ServiceLifecycleListener} for monitoring the uBus lifecycle. - * @return A {@link UPClient} instance. - * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_NAME} and - * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. - */ - public static @NonNull UPClient create(@NonNull Context context, @Nullable Handler handler, - @Nullable ServiceLifecycleListener listener) { - return new UPClient(context, null, null, new HandlerExecutor(handler), listener); - } - - /** - * Create an instance for a specified uEntity. - * - * @param context An application {@link Context}. This should not be null. If you are passing - * {@link ContextWrapper}, make sure that its base Context is non-null as well. - * Otherwise it will throw {@link NullPointerException}. - * @param entity A {@link UEntity} containing its name and major version, or null to use the - * first found declaration under application or service element - * in a manifest. - * @param handler A {@link Handler} on which callbacks should execute, or null to execute on - * the application's main thread. - * @param listener A {@link ServiceLifecycleListener} for monitoring the uBus lifecycle. - * @return A {@link UPClient} instance. - * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_NAME} and - * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. - */ - public static @NonNull UPClient create(@NonNull Context context, @Nullable UEntity entity, - @Nullable Handler handler, @Nullable ServiceLifecycleListener listener) { - return new UPClient(context, entity, null, new HandlerExecutor(handler), listener); - } - - /** - * Create an instance. - * - * @param context An application {@link Context}. This should not be null. If you are passing - * {@link ContextWrapper}, make sure that its base Context is non-null as well. - * Otherwise it will throw {@link NullPointerException}. - * @param executor An {@link Executor} on which callbacks should execute, or null to execute on - * the application's main thread. - * @param listener A {@link ServiceLifecycleListener} for monitoring the uBus lifecycle. - * @return A {@link UPClient} instance. - * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_NAME} and - * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. - */ - public static @NonNull UPClient create(@NonNull Context context, @Nullable Executor executor, - @Nullable ServiceLifecycleListener listener) { - return new UPClient(context, null, null, executor, listener); - } - - /** - * Create an instance for a specified uEntity. - * - * @param context An application {@link Context}. This should not be null. If you are passing - * {@link ContextWrapper}, make sure that its base Context is non-null as well. - * Otherwise it will throw {@link NullPointerException}. - * @param entity A {@link UEntity} containing its name and major version, or null to use the - * first found declaration under application or service element - * in a manifest. - * @param executor An {@link Executor} on which callbacks should execute, or null to execute on - * the application's main thread. - * @param listener A {@link ServiceLifecycleListener} for monitoring the uBus lifecycle. - * @return A {@link UPClient} instance. - * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_NAME} and - * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. - */ - public static @NonNull UPClient create(@NonNull Context context, @Nullable UEntity entity, - @Nullable Executor executor, @Nullable ServiceLifecycleListener listener) { - return new UPClient(context, entity, null, executor, listener); - } - - /** - * Connect to the uBus. - * - *

Requires {@link #PERMISSION_ACCESS_UBUS} permission to access this API. - * - *

An instance connected with this method should be disconnected from the uBus by calling - * {@link #disconnect()} before the passed {@link Context} is released. - * - * @return A {@link CompletionStage} used by a caller to receive the connection status. - */ - public @NonNull CompletionStage connect() { - return mUBusManager.connect(); - } - - /** - * Disconnect from the uBus. - * - *

All previously registered listeners will be automatically unregistered. - * - * @return A {@link CompletionStage} used by a caller to receive the disconnection status. - */ - public @NonNull CompletionStage disconnect() { - return mUBusManager.disconnect(); - } - - /** - * Check whether this instance is disconnected from the uBus or not. - * - * @return true if it is disconnected. - */ - public boolean isDisconnected() { - return mUBusManager.isDisconnected(); - } - - /** - * Check whether this instance is already connecting to the uBus or not. - * - * @return true if it is connecting. - */ - public boolean isConnecting() { - return mUBusManager.isConnecting(); - } - - /** - * Check whether the uBus is connected or not. This will return false if it - * is still connecting. - * - * @return true if is is connected. - */ - public boolean isConnected() { - return mUBusManager.isConnected(); - } - - private void setRegistrationExpired() { - synchronized (mRegistrationLock) { - mRegistrationExpired = true; - } - } - - private void renewRegistration() { - synchronized (mRegistrationLock) { - if (mRegistrationExpired) { - mGenericListeners.keySet().forEach(mUBusManager::enableDispatching); - mRequestListeners.keySet().forEach(mUBusManager::enableDispatching); - mRegistrationExpired = false; - } - } - } - - private void release() { - synchronized (mRegistrationLock) { - mRequests.values().forEach(requestFuture -> requestFuture.completeExceptionally( - new UStatusException(UCode.CANCELLED, "Service is disconnected"))); - mRequests.clear(); - mGenericListeners.clear(); - mRequestListeners.clear(); - mRegistrationExpired = false; - } - } - - /** - * Get a uEntity associated with this instance. - * - * @return A {@link UEntity}. - */ - public @NonNull UEntity getEntity() { - return mUri.getEntity(); - } - - /** - * Get a URI associated with this instance. - * - * @return A {@link UUri}. - */ - public @NonNull UUri getUri() { - return mUri; - } - - @VisibleForTesting - @NonNull String getTag() { - return mTag; - } - - @VisibleForTesting - void setLoggable(int level) { - mVerboseLoggable = level <= Log.VERBOSE; - } - - @VisibleForTesting - ConnectionCallback getConnectionCallback() { - return mConnectionCallback; - } - - @VisibleForTesting - UListener getListener() { - return mListener; - } - - - /** - * Transmit a message. - * - * @param message A {@link UMessage} to be sent. - * @return A {@link UStatus} which contains a result code and other details. - */ - @Override - public @NonNull UStatus send(@NonNull UMessage message) { - return mUBusManager.send(message); - } - - /** - * Register a listener for a particular URI to be notified when a message with that URI is received. - * - *

Only one listener is allowed to be registered per method URI. For a topic URI, - * multiple listeners are allowed to be registered. But in order to start receiving - * published data a client needs to subscribe to that topic. - * - * @param uri A {@link UUri} associated with either topic or method. - * @param listener A {@link UListener} which needs to be registered. - * @return A {@link UStatus} which contains a result code and other details. - */ - - @Override - public @NonNull UStatus registerListener(@NonNull UUri uri, @NonNull UListener listener) { - return isRpcMethod(uri) ? registerRequestListener(uri, listener) : registerGenericListener(uri, listener); - } - - /** - * Unregister a listener from a particular URI. - * - *

If this listener wasn't registered, nothing will happen. - * - * @param uri A {@link UUri} associated with either topic or method. - * @param listener A {@link UListener} which needs to be unregistered. - * @return A {@link UStatus} which contains a result code and other details. - */ - @Override - public @NonNull UStatus unregisterListener(@NonNull UUri uri, @NonNull UListener listener) { - return isRpcMethod(uri) ? unregisterRequestListener(uri, listener) : unregisterGenericListener(uri, listener); - } - - /** - * Unregister a listener from all. - * - *

If this listener wasn't registered, nothing will happen. - * - * @param listener A {@link UListener} which needs to be unregistered. - * @return A {@link UStatus} which contains a result code and other details. - */ - public @NonNull UStatus unregisterListener(@NonNull UListener listener) { - try { - checkNotNull(listener, "Listener is null"); - synchronized (mRegistrationLock) { - mGenericListeners.keySet().removeIf(topic -> unregisterGenericListenerLocked(topic, listener)); - mRequestListeners.entrySet().removeIf(entry -> { - if (entry.getValue() == listener) { - mUBusManager.disableDispatchingQuietly(entry.getKey()); - return true; - } else { - return false; - } - }); - } - return STATUS_OK; - } catch (Exception e) { - return toStatus(e); - } - } - - private @NonNull UStatus registerGenericListener(@NonNull UUri topic, @NonNull UListener listener) { - try { - checkArgument(!isEmpty(topic), "Topic is empty"); - checkNotNull(listener, "Listener is null"); - synchronized (mRegistrationLock) { - Set listeners = mGenericListeners.get(topic); - if (listeners == null) { - listeners = new HashSet<>(); - } - if (listeners.isEmpty()) { - final UStatus status = mUBusManager.enableDispatching(topic); - if (!isOk(status)) { - return status; - } - mGenericListeners.put(topic, listeners); - } - if (listeners.add(listener) && listeners.size() > 1) { - mCallbackExecutor.execute(() -> { - final UMessage event = mUBusManager.getLastMessage(topic); - if (event != null) { - listener.onReceive(event); - } - }); - } - return STATUS_OK; - } - } catch (Exception e) { - return toStatus(e); - } - } - - private @NonNull UStatus unregisterGenericListener(@NonNull UUri topic, @NonNull UListener listener) { - try { - checkArgument(!isEmpty(topic), "Topic is empty"); - checkNotNull(listener, "Listener is null"); - synchronized (mRegistrationLock) { - if (unregisterGenericListenerLocked(topic, listener)) { - mGenericListeners.remove(topic); - } - } - return STATUS_OK; - } catch (Exception e) { - return toStatus(e); - } - } - - private boolean unregisterGenericListenerLocked(@NonNull UUri topic, @NonNull UListener listener) { - final Set listeners = mGenericListeners.get(topic); - if (listeners != null && listeners.contains(listener)) { - listeners.remove(listener); - if (listeners.isEmpty()) { - // No listener left for this topic - mUBusManager.disableDispatchingQuietly(topic); - return true; // The entry MUST be removed - } - } - return false; - } - - private @NonNull UStatus registerRequestListener(@NonNull UUri methodUri, @NonNull UListener listener) { - try { - checkNotNull(listener, "Listener is null"); - synchronized (mRegistrationLock) { - final UListener currentListener = mRequestListeners.get(methodUri); - if (currentListener == listener) { - return STATUS_OK; - } - checkArgument(currentListener == null, UCode.ALREADY_EXISTS, "Listener is already registered"); - final UStatus status = mUBusManager.enableDispatching(methodUri); - if (isOk(status)) { - mRequestListeners.put(methodUri, listener); - } - return status; - } - } catch (Exception e) { - return toStatus(e); - } - } - - private @NonNull UStatus unregisterRequestListener(@NonNull UUri methodUri, @NonNull UListener listener) { - try { - checkNotNull(listener, "Listener is null"); - synchronized (mRegistrationLock) { - if (mRequestListeners.remove(methodUri, listener)) { - mUBusManager.disableDispatchingQuietly(methodUri); - } - return STATUS_OK; - } - } catch (Exception e) { - return toStatus(e); - } - } - - /** - * Asynchronously invoke a method (send an RPC request) and receive a response. - * - * @param methodUri A {@link UUri} associated with a method. - * @param requestPayload A {@link UPayload} to be supplied with a request. - * @param options {@link CallOptions} containing various invocation parameters. - * @return A {@link CompletionStage} used by a caller to receive a response. - */ - @Override - public @NonNull CompletionStage invokeMethod(@NonNull UUri methodUri, @NonNull UPayload requestPayload, - @NonNull CallOptions options) { - try { - checkArgument(!isEmpty(methodUri), "Method URI is empty"); - checkNotNull(requestPayload, "Payload is null"); - checkNotNull(options, "Options cannot be null"); - final UPriority priority = checkPriority(options); - final int timeout = checkArgumentPositive(options.getTtl(), "Timeout is not positive"); - final UAttributesBuilder builder = UAttributesBuilder.request(mResponseUri, methodUri, priority, timeout); - if (options.hasToken()) { - builder.withToken(options.getToken()); - } - final UMessage requestMessage = UMessage.newBuilder() - .setPayload(requestPayload) - .setAttributes(builder.build()) - .build(); - return mRequests.compute(requestMessage.getAttributes().getId(), (requestId, currentRequest) -> { - checkArgument(currentRequest == null, UCode.ABORTED, "Duplicated request found"); - final UStatus status = send(requestMessage); - if (isOk(status)) { - return buildClientResponseFuture(requestMessage); - } else { - throw new UStatusException(status); - } - }); - } catch (Exception e) { - return CompletableFuture.failedFuture(e); - } - } - - private static @NonNull UPriority checkPriority(@NonNull CallOptions options) { - final UPriority priority = options.getPriority(); - checkArgument(priority.getNumber() >= UPriority.UPRIORITY_CS4.getNumber(), - "Priority must be equal or higher than " + UPriority.UPRIORITY_CS4); - return priority; - } - - private @NonNull CompletableFuture buildClientResponseFuture(@NonNull UMessage requestMessage) { - final CompletableFuture responseFuture = new CompletableFuture() - .orTimeout(requestMessage.getAttributes().getTtl(), TimeUnit.MILLISECONDS); - responseFuture.whenComplete((responseMessage, exception) -> - mRequests.remove(requestMessage.getAttributes().getId())); - return responseFuture; - } - - private void handleMessage(@NonNull UMessage message) { - if (mVerboseLoggable) { - Log.v(mTag, join(Key.EVENT, MESSAGE_RECEIVED, Key.MESSAGE, stringify(message))); - } - final UAttributes attributes = message.getAttributes(); - final UAttributesValidator validator = getValidator(attributes); - final ValidationResult result = validator.validate(attributes); - if (result.isFailure()) { - Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(message), Key.REASON, result.getMessage())); - return; - } - if (validator.isExpired(attributes)) { // Do we need to check expiration? Should be done by the service... - Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(message), Key.REASON, "Expired")); - return; - } - switch (attributes.getType()) { - case UMESSAGE_TYPE_PUBLISH, UMESSAGE_TYPE_NOTIFICATION -> handleGenericMessage(message); - case UMESSAGE_TYPE_REQUEST -> handleRequestMessage(message); - case UMESSAGE_TYPE_RESPONSE -> handleResponseMessage(message); - default -> Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(message), Key.REASON, "Unknown type")); - } - } - - private void handleGenericMessage(@NonNull UMessage message) { - if (message.getAttributes().hasSink()) { - final UEntity entity = message.getAttributes().getSink().getEntity(); - if (!entity.equals(mUri.getEntity())) { - Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(message), Key.REASON, "Wrong sink")); - return; - } - } - mCallbackExecutor.execute(() -> { - final UUri topic = message.getAttributes().getSource(); - final Set listeners; - synchronized (mRegistrationLock) { - listeners = new ArraySet<>(mGenericListeners.get(topic)); - if (listeners.isEmpty()) { - Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(message), Key.REASON, "No listener")); - } - } - listeners.forEach(listener -> listener.onReceive(message)); - }); - } - - private void handleRequestMessage(@NonNull UMessage requestMessage) { - mCallbackExecutor.execute(() -> { - final UUri methodUri = requestMessage.getAttributes().getSink(); - final UListener listener; - synchronized (mRegistrationLock) { - listener = mRequestListeners.get(methodUri); - if (listener == null) { - Log.w(mTag, join(Key.EVENT, MESSAGE_DROPPED, Key.MESSAGE, stringify(requestMessage), Key.REASON, "No listener")); - return; - } - } - listener.onReceive(requestMessage); - }); - } - - private void handleResponseMessage(@NonNull UMessage responseMessage) { - final UAttributes responseAttributes = responseMessage.getAttributes(); - final CompletableFuture responseFuture = mRequests.remove(responseAttributes.getReqid()); - if (responseFuture == null) { - return; - } - if (responseAttributes.hasCommstatus()) { - final UCode code = responseAttributes.getCommstatus(); - if (code != UCode.OK) { - responseFuture.completeExceptionally(new UStatusException(code, "Communication error [" + code + "]")); - return; - } - } - responseFuture.complete(responseMessage); - } -} diff --git a/library/src/main/java/org/eclipse/uprotocol/common/UStatusException.java b/library/src/main/java/org/eclipse/uprotocol/common/UStatusException.java deleted file mode 100644 index 6a45d8d..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/common/UStatusException.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.common; - -import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UStatus; - -/** - * The unchecked exception which carries uProtocol error model. - */ -public class UStatusException extends RuntimeException { - private final UStatus mStatus; - - /** - * Constructs an instance. - * - * @param code An error {@link UCode}. - * @param message An error message. - */ - public UStatusException(UCode code, String message) { - this(buildStatus(code, message), null); - } - - /** - * Constructs an instance. - * - * @param code An error {@link UCode}. - * @param message An error message. - * @param cause An exception that caused this one. - */ - public UStatusException(UCode code, String message, Throwable cause) { - this(buildStatus(code, message), cause); - } - - /** - * Constructs an instance. - * - * @param status An error {@link UStatus}. - */ - public UStatusException(UStatus status) { - this(status, null); - } - - /** - * Constructs an instance. - * - * @param status An error {@link UStatus}. - * @param cause An exception that caused this one. - */ - public UStatusException(UStatus status, Throwable cause) { - super((status != null) ? status.getMessage() : "", cause); - mStatus = (status != null) ? status : buildStatus(UCode.UNKNOWN); - } - - /** - * Get the error status. - * @return The error {@link UStatus}. - */ - public @NonNull UStatus getStatus() { - return mStatus; - } - - /** - * Get the error code. - * @return The error {@link UCode}. - */ - public @NonNull UCode getCode() { - return mStatus.getCode(); - } - - /** - * Get the error message. - * @return The error message. - */ - @Override - public @Nullable String getMessage() { - return mStatus.getMessage(); - } -} diff --git a/library/src/main/java/org/eclipse/uprotocol/common/util/UStatusUtils.java b/library/src/main/java/org/eclipse/uprotocol/common/util/UStatusUtils.java index 4a334ca..b513e5e 100644 --- a/library/src/main/java/org/eclipse/uprotocol/common/util/UStatusUtils.java +++ b/library/src/main/java/org/eclipse/uprotocol/common/util/UStatusUtils.java @@ -27,11 +27,10 @@ import android.text.TextUtils; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.protobuf.InvalidProtocolBufferException; -import org.eclipse.uprotocol.common.UStatusException; +import org.eclipse.uprotocol.communication.UStatusException; import org.eclipse.uprotocol.v1.UCode; import org.eclipse.uprotocol.v1.UStatus; @@ -52,7 +51,7 @@ public interface UStatusUtils { * @param status A {@link UStatus} with an error code. * @return true if it contains {@link UCode#OK}. */ - static boolean isOk(@Nullable UStatus status) { + static boolean isOk(UStatus status) { return status != null && status.getCodeValue() == UCode.OK_VALUE; } @@ -63,7 +62,7 @@ static boolean isOk(@Nullable UStatus status) { * @param code An int value of a {@link UCode} to check. * @return true if it contains the same code. */ - static boolean hasCode(@Nullable UStatus status, int code) { + static boolean hasCode(UStatus status, int code) { return status != null && status.getCodeValue() == code; } @@ -74,7 +73,7 @@ static boolean hasCode(@Nullable UStatus status, int code) { * @param code A {@link UCode} to check. * @return true if it contains the same code. */ - static boolean hasCode(@Nullable UStatus status, @NonNull UCode code) { + static boolean hasCode(UStatus status, @NonNull UCode code) { return status != null && status.getCodeValue() == code.getNumber(); } @@ -86,7 +85,7 @@ static boolean hasCode(@Nullable UStatus status, @NonNull UCode code) { * @return A {@link UCode} from the given status if is not null, * otherwise defaultCode. */ - static @NonNull UCode getCode(@Nullable UStatus status, @NonNull UCode defaultCode) { + static @NonNull UCode getCode(UStatus status, @NonNull UCode defaultCode) { return (status != null) ? status.getCode() : defaultCode; } @@ -97,7 +96,7 @@ static boolean hasCode(@Nullable UStatus status, @NonNull UCode code) { * @return A {@link UCode} from the given status if is not null, * otherwise {@link UCode#UNKNOWN}. */ - static @NonNull UCode getCode(@Nullable UStatus status) { + static @NonNull UCode getCode(UStatus status) { return getCode(status, UCode.UNKNOWN); } @@ -116,7 +115,7 @@ static boolean hasCode(@Nullable UStatus status, @NonNull UCode code) { return UStatus.newBuilder().setCode(code); } - private static @NonNull UStatus.Builder newStatusBuilder(@NonNull UCode code, @Nullable String message) { + private static @NonNull UStatus.Builder newStatusBuilder(@NonNull UCode code, String message) { UStatus.Builder builder = newStatusBuilder(code); if (message != null) { builder.setMessage(message); @@ -141,7 +140,7 @@ static boolean hasCode(@Nullable UStatus status, @NonNull UCode code) { * @param message A message to set. * @return A {@link UStatus} with the given code and message. */ - static @NonNull UStatus buildStatus(@NonNull UCode code, @Nullable String message) { + static @NonNull UStatus buildStatus(@NonNull UCode code, String message) { return newStatusBuilder(code, message).build(); } @@ -210,7 +209,7 @@ static void checkStatusOk(@NonNull UStatus status) { * @param errorMessage A message to use if the check fails. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if expression is false. */ - static void checkArgument(boolean expression, @Nullable String errorMessage) { + static void checkArgument(boolean expression, @NonNull String errorMessage) { if (!expression) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -224,7 +223,7 @@ static void checkArgument(boolean expression, @Nullable String errorMessage) { * @param errorMessage A message to use if the check fails. * @throws UStatusException containing errorCode if expression is false. */ - static void checkArgument(boolean expression, @NonNull UCode errorCode, @Nullable String errorMessage) { + static void checkArgument(boolean expression, @NonNull UCode errorCode, @NonNull String errorMessage) { if (!expression) { throw new UStatusException(errorCode, errorMessage); } @@ -238,7 +237,7 @@ static void checkArgument(boolean expression, @NonNull UCode errorCode, @Nullabl * @return A validated value. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if value is not positive. */ - static int checkArgumentPositive(int value, @Nullable String errorMessage) { + static int checkArgumentPositive(int value, @NonNull String errorMessage) { if (value <= 0) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -254,7 +253,7 @@ static int checkArgumentPositive(int value, @Nullable String errorMessage) { * @return A validated value. * @throws UStatusException containing errorCode if value is not positive. */ - static int checkArgumentPositive(int value, @NonNull UCode errorCode, @Nullable String errorMessage) { + static int checkArgumentPositive(int value, @NonNull UCode errorCode, @NonNull String errorMessage) { if (value <= 0) { throw new UStatusException(errorCode, errorMessage); } @@ -269,7 +268,7 @@ static int checkArgumentPositive(int value, @NonNull UCode errorCode, @Nullable * @return A validated value. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if value is negative. */ - static int checkArgumentNonNegative(int value, @Nullable String errorMessage) { + static int checkArgumentNonNegative(int value, @NonNull String errorMessage) { if (value < 0) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -285,7 +284,7 @@ static int checkArgumentNonNegative(int value, @Nullable String errorMessage) { * @return A validated value. * @throws UStatusException containing errorCode if value is negative. */ - static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullable String errorMessage) { + static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @NonNull String errorMessage) { if (value < 0) { throw new UStatusException(errorCode, errorMessage); } @@ -300,7 +299,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @return A validated string. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if string is empty or null. */ - static @NonNull T checkStringNotEmpty(T string, @Nullable String errorMessage) { + static @NonNull T checkStringNotEmpty(T string, @NonNull String errorMessage) { if (TextUtils.isEmpty(string)) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -317,7 +316,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @throws UStatusException containing errorCode if string is empty or null. */ static @NonNull T checkStringNotEmpty(T string, @NonNull UCode errorCode, - @Nullable String errorMessage) { + @NonNull String errorMessage) { if (TextUtils.isEmpty(string)) { throw new UStatusException(errorCode, errorMessage); } @@ -333,8 +332,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @return A validated string1. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if strings are not equal. */ - static @NonNull T checkStringEquals(T string1, T string2, - @Nullable String errorMessage) { + static @NonNull T checkStringEquals(T string1, T string2, @NonNull String errorMessage) { if (!TextUtils.equals(string1, string2)) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -352,7 +350,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @throws UStatusException containing errorCode if strings are not equal. */ static @NonNull T checkStringEquals(T string1, @NonNull T string2, - @NonNull UCode errorCode, @Nullable String errorMessage) { + @NonNull UCode errorCode, @NonNull String errorMessage) { if (!TextUtils.equals(string1, string2)) { throw new UStatusException(errorCode, errorMessage); } @@ -367,7 +365,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @return A validated reference. * @throws UStatusException containing {@link UCode#INVALID_ARGUMENT} if reference is null. */ - static @NonNull T checkNotNull(@Nullable T reference, @Nullable String errorMessage) { + static @NonNull T checkNotNull(T reference, @NonNull String errorMessage) { if (reference == null) { throw new UStatusException(UCode.INVALID_ARGUMENT, errorMessage); } @@ -383,8 +381,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @return A validated reference. * @throws UStatusException containing errorCode if reference is null. */ - static @NonNull T checkNotNull(@Nullable T reference, @NonNull UCode errorCode, - @Nullable String errorMessage) { + static @NonNull T checkNotNull(T reference, @NonNull UCode errorCode, @NonNull String errorMessage) { if (reference == null) { throw new UStatusException(errorCode, errorMessage); } @@ -398,7 +395,7 @@ static int checkArgumentNonNegative(int value, @NonNull UCode errorCode, @Nullab * @param errorMessage A message to use if the check fails. * @throws UStatusException containing {@link UCode#FAILED_PRECONDITION} if expression is false. */ - static void checkState(boolean expression, @Nullable String errorMessage) { + static void checkState(boolean expression, @NonNull String errorMessage) { if (!expression) { throw new UStatusException(UCode.FAILED_PRECONDITION, errorMessage); } @@ -412,7 +409,7 @@ static void checkState(boolean expression, @Nullable String errorMessage) { * @param errorMessage A message to use if the check fails. * @throws UStatusException containing errorCode if expression is false. */ - static void checkState(boolean expression, @NonNull UCode errorCode, @Nullable String errorMessage) { + static void checkState(boolean expression, @NonNull UCode errorCode, @NonNull String errorMessage) { if (!expression) { throw new UStatusException(errorCode, errorMessage); } diff --git a/library/src/main/java/org/eclipse/uprotocol/common/util/log/Formatter.java b/library/src/main/java/org/eclipse/uprotocol/common/util/log/Formatter.java index 4497035..1835b22 100644 --- a/library/src/main/java/org/eclipse/uprotocol/common/util/log/Formatter.java +++ b/library/src/main/java/org/eclipse/uprotocol/common/util/log/Formatter.java @@ -26,20 +26,20 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.nullToEmpty; -import android.annotation.SuppressLint; - import androidx.annotation.NonNull; -import org.eclipse.uprotocol.uri.serializer.LongUriSerializer; -import org.eclipse.uprotocol.uuid.serializer.LongUuidSerializer; +import org.eclipse.uprotocol.uri.serializer.UriSerializer; +import org.eclipse.uprotocol.uri.validator.UriFilter; +import org.eclipse.uprotocol.uuid.serializer.UuidSerializer; import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UEntity; import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UResource; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUID; import org.eclipse.uprotocol.v1.UUri; +import java.time.Duration; +import java.util.Locale; + /** * The formatter utility to be used for logging key-value pairs. */ @@ -191,62 +191,53 @@ private static void appendPairsSeparator(@NonNull StringBuilder builder) { } /** - * Convert a {@link UUID} into a string. + * Format an error message with optional arguments. * - * @param id A {@link UUID} to convert. - * @return A formatted string. + * @param message A message. + * @param cause A {@link Throwable} that caused an error. + * @param args A variable argument list of key-value pairs, like "key1, value1, key2, value2, ...". + * @return A formatted string containing an error message, a cause's message and + * other given key-value pairs. */ - static @NonNull String stringify(UUID id) { - return LongUuidSerializer.instance().serialize(id); + static @NonNull String error(@NonNull String message, @NonNull Throwable cause, Object... args) { + final StringBuilder builder = new StringBuilder(); + joinAndAppend(builder, Key.ERROR, message); + joinAndAppend(builder, Key.REASON, cause.getMessage()); + joinAndAppend(builder, args); + return builder.toString(); } /** - * Convert a {@link UEntity} into a string containing arbitrary fields. + * Convert a {@link UUID} into a string. * - * @param entity A {@link UEntity} to convert. + * @param id A {@link UUID} to convert. * @return A formatted string. */ - static @NonNull String stringify(UEntity entity) { - if (entity == null) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - sb.append(entity.getName()); - if (entity.hasVersionMajor()) { - sb.append('/').append(entity.getVersionMajor()); - } - return sb.toString(); + static @NonNull String stringify(UUID id) { + return UuidSerializer.serialize(id); } /** - * Convert a {@link UResource} into a string containing arbitrary fields. + * Convert a {@link UUri} into a string. * - * @param resource A {@link UResource} to convert. + * @param uri A {@link UUri} to convert. * @return A formatted string. */ - static @NonNull String stringify(UResource resource) { - if (resource == null) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - sb.append(resource.getName()); - if (resource.hasInstance()) { - sb.append('.').append(resource.getInstance()); - } - if (resource.hasMessage()) { - sb.append('#').append(resource.getMessage()); - } - return sb.toString(); + static @NonNull String stringify(UUri uri) { + return UriSerializer.serialize(uri); } /** - * Convert a {@link UUri} into a string. + * Convert a {@link UriFilter} into a string. * - * @param uri A {@link UUri} to convert. + * @param filter A {@link UriFilter} to convert. * @return A formatted string. */ - static @NonNull String stringify(UUri uri) { - return LongUriSerializer.instance().serialize(uri); + static @NonNull String stringify(UriFilter filter) { + if (filter == null) { + return ""; + } + return joinGrouped(Key.SOURCE, stringify(filter.source()), Key.SINK, stringify(filter.sink())); } /** @@ -287,13 +278,24 @@ private static void appendPairsSeparator(@NonNull StringBuilder builder) { * @param bytes A byte count. * @return A formatted string such as "5.0 MB". */ - @SuppressLint("DefaultLocale") static @NonNull String toPrettyMemory(long bytes) { long unit = 1024; if (bytes < unit) { return bytes + " B"; } int exp = (int) (Math.log(bytes) / Math.log(unit)); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), "KMGTPE".charAt(exp - 1)); + return String.format(Locale.ROOT, "%.1f %sB", bytes / Math.pow(unit, exp), "KMGTPE".charAt(exp - 1)); + } + + /** + * Convert a duration to a human readable string. + * + * @param millis A duration in milliseconds. + * @return A formatted string such as "2h 30m 0s". + */ + static @NonNull String toPrettyDuration(long millis) { + final Duration duration = Duration.ofMillis(millis); + return String.format(Locale.ROOT, "%dh %dm %ds", + duration.toHours(), duration.toMinutesPart(), duration.toSecondsPart()); } } diff --git a/library/src/main/java/org/eclipse/uprotocol/common/util/log/Key.java b/library/src/main/java/org/eclipse/uprotocol/common/util/log/Key.java index 33c0aa7..a732600 100644 --- a/library/src/main/java/org/eclipse/uprotocol/common/util/log/Key.java +++ b/library/src/main/java/org/eclipse/uprotocol/common/util/log/Key.java @@ -46,9 +46,12 @@ public interface Key { String DUMP = "dump"; String DURATION = "duration"; String ENTITY = "entity"; + String ERROR = "error"; String EVENT = "event"; + String EXPIRY = "expiry"; String FAILURE = "failure"; String FILENAME = "filename"; + String FILTER = "filter"; String FORMAT = "format"; String ID = "id"; String INSTANCE = "instance"; @@ -67,10 +70,12 @@ public interface Key { String PATH = "path"; String PAYLOAD = "payload"; String PERCENTAGE = "percentage"; + String PERIOD = "period"; String PERMISSIONS = "permissions"; String PID = "pid"; String PRIORITY = "priority"; String REASON = "reason"; + String RECORD = "resource"; String REFERENCE = "reference"; String REQUEST = "request"; String REQUEST_ID = "requestId"; diff --git a/library/src/main/java/org/eclipse/uprotocol/core/ubus/UBusManager.java b/library/src/main/java/org/eclipse/uprotocol/core/ubus/UBusManager.java index 6d81076..2825404 100644 --- a/library/src/main/java/org/eclipse/uprotocol/core/ubus/UBusManager.java +++ b/library/src/main/java/org/eclipse/uprotocol/core/ubus/UBusManager.java @@ -25,7 +25,6 @@ import static android.content.Context.BIND_AUTO_CREATE; -import static org.eclipse.uprotocol.UPClient.TAG_GROUP; import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; import static org.eclipse.uprotocol.common.util.UStatusUtils.checkNotNull; @@ -36,6 +35,7 @@ import static org.eclipse.uprotocol.common.util.log.Formatter.status; import static org.eclipse.uprotocol.common.util.log.Formatter.stringify; import static org.eclipse.uprotocol.common.util.log.Formatter.tag; +import static org.eclipse.uprotocol.transport.UTransportAndroid.TAG; import static java.util.Objects.requireNonNull; @@ -51,19 +51,17 @@ import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import org.eclipse.uprotocol.client.R; -import org.eclipse.uprotocol.common.UStatusException; import org.eclipse.uprotocol.common.util.log.Key; +import org.eclipse.uprotocol.communication.UStatusException; +import org.eclipse.uprotocol.transport.R; import org.eclipse.uprotocol.transport.UListener; +import org.eclipse.uprotocol.uri.validator.UriFilter; import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; import org.eclipse.uprotocol.v1.UMessage; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.v1.internal.ParcelableUEntity; import org.eclipse.uprotocol.v1.internal.ParcelableUMessage; import org.eclipse.uprotocol.v1.internal.ParcelableUUri; @@ -83,8 +81,6 @@ public final class UBusManager { public static final String ACTION_BIND_UBUS = "uprotocol.action.BIND_UBUS"; - public static final int FLAG_BLOCK_AUTO_FETCH = 0x00000001; - private static final int REBIND_BACKOFF_EXPONENT_MAX = 5; private static final int REBIND_BACKOFF_BASE = 2; @@ -101,7 +97,7 @@ public final class UBusManager { private @interface StateTypeEnum {} private final Context mContext; - private final UEntity mEntity; + private final UUri mClientUri; private final IBinder mClientToken = new Binder(); private final ConnectionCallback mConnectionCallback; private final UListener mListener; @@ -227,14 +223,14 @@ public void onReceive(ParcelableUMessage data) { } }; - public UBusManager(@NonNull Context context, @NonNull UEntity entity, @NonNull ConnectionCallback callback, + public UBusManager(@NonNull Context context, @NonNull UUri clientUri, @NonNull ConnectionCallback callback, @NonNull UListener listener) { mContext = requireNonNull(context); - mEntity = requireNonNull(entity); + mClientUri = requireNonNull(clientUri); mConnectionCallback = requireNonNull(callback); mListener = requireNonNull(listener); mServiceConfig = mContext.getString(R.string.config_UBusService); - mTag = tag(entity.getName(), TAG_GROUP); + mTag = tag(TAG, Integer.toHexString(clientUri.getUeId())); mDebugLoggable = Log.isLoggable(mTag, Log.DEBUG); mVerboseLoggable = Log.isLoggable(mTag, Log.VERBOSE); } @@ -249,7 +245,7 @@ public UBusManager(@NonNull Context context, @NonNull UEntity entity, @NonNull C if (isOk(status)) { setConnectionStateLocked(STATE_CONNECTING); mConnectionFuture = new CompletableFuture<>(); - return mConnectionFuture; + return mConnectionFuture.thenApplyAsync(Function.identity()); // Avoid deadlock } else { handleServiceDisconnectLocked(); mConnectionFuture = null; @@ -393,7 +389,7 @@ long calculateRebindDelaySeconds() { UStatus status; try { status = service.registerClient(mContext.getPackageName(), - new ParcelableUEntity(mEntity), mClientToken, 0, mServiceListener).getWrapped(); + new ParcelableUUri(mClientUri), mClientToken, 0, mServiceListener).getWrapped(); } catch (Exception e) { status = toStatus(e); } @@ -431,45 +427,40 @@ long calculateRebindDelaySeconds() { return status; } - public @NonNull UStatus enableDispatching(@NonNull UUri uri) { + public @NonNull UStatus enableDispatching(@NonNull UriFilter filter) { UStatus status; try { - status = getServiceOrThrow().enableDispatching(new ParcelableUUri(uri), 0, mClientToken).getWrapped(); + final ParcelableUUri sourceFilter = new ParcelableUUri(filter.source()); + final ParcelableUUri sinkFilter = new ParcelableUUri(filter.sink()); + status = getServiceOrThrow().enableDispatching(sourceFilter, sinkFilter,0, mClientToken).getWrapped(); } catch (Exception e) { status = toStatus(e); } if (isDebugLoggable(status)) { - Log.println(debugOrError(status), mTag, status("enableDispatching", status, Key.URI, stringify(uri))); + Log.println(debugOrError(status), mTag, status("enableDispatching", status, + Key.SOURCE, stringify(filter.source()), Key.SINK, stringify(filter.sink()))); } return status; } - public @NonNull UStatus disableDispatching(@NonNull UUri uri) { + public @NonNull UStatus disableDispatching(@NonNull UriFilter filter) { UStatus status; try { - status = getServiceOrThrow().disableDispatching(new ParcelableUUri(uri), 0, mClientToken).getWrapped(); + final ParcelableUUri sourceFilter = new ParcelableUUri(filter.source()); + final ParcelableUUri sinkFilter = new ParcelableUUri(filter.sink()); + status = getServiceOrThrow().disableDispatching(sourceFilter, sinkFilter, 0, mClientToken).getWrapped(); } catch (Exception e) { status = toStatus(e); } if (isDebugLoggable(status)) { - Log.println(debugOrError(status), mTag, status("disableDispatching", status, Key.URI, stringify(uri))); + Log.println(debugOrError(status), mTag, status("disableDispatching", status, + Key.SOURCE, stringify(filter.source()), Key.SINK, stringify(filter.sink()))); } return status; } - public void disableDispatchingQuietly(@NonNull UUri uri) { - disableDispatching(uri); - } - - public @Nullable UMessage getLastMessage(@NonNull UUri topic) { - try { - final ParcelableUMessage[] bundle = getServiceOrThrow() - .pull(new ParcelableUUri(topic), 1, 0, mClientToken); - return (bundle != null && bundle.length > 0) ? bundle[0].getWrapped() : null; - } catch (Exception e) { - Log.e(mTag, status("getLastMessage", toStatus(e), Key.URI, stringify(topic))); - return null; - } + public void disableDispatchingQuietly(@NonNull UriFilter filter) { + disableDispatching(filter); } private boolean isDebugLoggable(@NonNull UStatus status) { diff --git a/library/src/main/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscovery.java b/library/src/main/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscovery.java deleted file mode 100644 index 5b026eb..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscovery.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.udiscovery.v3; - -import static org.eclipse.uprotocol.rpc.RpcMapper.mapResponse; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; - -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAuthority; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; - -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -public class UDiscovery { - public static final UEntity SERVICE = UEntity.newBuilder() - .setName("core.udiscovery") - .setVersionMajor(3) - .build(); - public static final String METHOD_LOOKUP_URI = "LookupUri"; - public static final String METHOD_UPDATE_NODE = "UpdateNode"; - public static final String METHOD_FIND_NODES = "FindNodes"; - public static final String METHOD_FIND_NODE_PROPERTIES = "FindNodeProperties"; - public static final String METHOD_DELETE_NODES = "DeleteNodes"; - public static final String METHOD_ADD_NODES = "AddNodes"; - public static final String METHOD_UPDATE_PROPERTY = "UpdateProperty"; - public static final String METHOD_REGISTER_FOR_NOTIFICATIONS = "RegisterForNotifications"; - public static final String METHOD_UNREGISTER_FOR_NOTIFICATIONS = "UnregisterForNotifications"; - public static final String METHOD_RESOLVE_URI = "ResolveUri"; - - private static final CallOptions DEFAULT_OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(10_000) - .build(); - - private UDiscovery() {} - - public static UDiscovery.Stub newStub(RpcClient proxy) { - return newStub(proxy, null, DEFAULT_OPTIONS); - } - - public static UDiscovery.Stub newStub(RpcClient proxy, CallOptions options) { - return newStub(proxy, null, options); - } - - public static UDiscovery.Stub newStub(RpcClient proxy, UAuthority authority, CallOptions options) { - return new UDiscovery.Stub(proxy, authority, options); - } - - public static class Stub { - private final RpcClient proxy; - private final UAuthority authority; - private final CallOptions options; - - private Stub(RpcClient proxy, UAuthority authority, CallOptions options) { - this.proxy = proxy; - this.authority = authority; - this.options = options; - } - - private UUri buildUri(String method) { - final UUri.Builder builder = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest(method)); - if (authority != null) { - builder.setAuthority(authority); - } - return builder.build(); - } - - public Optional getAuthority() { - return (authority != null) ? Optional.of(authority) : Optional.empty(); - } - - public CallOptions getOptions() { - return options; - } - - public CompletionStage lookupUri(UUri request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_LOOKUP_URI), packToAny(request), options), LookupUriResponse.class); - } - - public CompletionStage updateNode(UpdateNodeRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_UPDATE_NODE), packToAny(request), options), UStatus.class); - } - - public CompletionStage findNodes(FindNodesRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_FIND_NODES), packToAny(request), options), FindNodesResponse.class); - } - - public CompletionStage findNodeProperties(FindNodePropertiesRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_FIND_NODE_PROPERTIES), packToAny(request), options), FindNodePropertiesResponse.class); - } - - public CompletionStage deleteNodes(DeleteNodesRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_DELETE_NODES), packToAny(request), options), UStatus.class); - } - - public CompletionStage addNodes(AddNodesRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_ADD_NODES), packToAny(request), options), UStatus.class); - } - - public CompletionStage updateProperty(UpdatePropertyRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_UPDATE_PROPERTY), packToAny(request), options), UStatus.class); - } - - public CompletionStage registerForNotifications(NotificationsRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_REGISTER_FOR_NOTIFICATIONS), packToAny(request), options), UStatus.class); - } - - public CompletionStage unregisterForNotifications(NotificationsRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_UNREGISTER_FOR_NOTIFICATIONS), packToAny(request), options), UStatus.class); - } - - public CompletionStage resolveUri(ResolveUriRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_RESOLVE_URI), packToAny(request), options), ResolveUriResponse.class); - } - } -} diff --git a/library/src/main/java/org/eclipse/uprotocol/core/usubscription/v3/USubscription.java b/library/src/main/java/org/eclipse/uprotocol/core/usubscription/v3/USubscription.java deleted file mode 100644 index fdb070d..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/core/usubscription/v3/USubscription.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.usubscription.v3; - -import static org.eclipse.uprotocol.rpc.RpcMapper.mapResponse; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; - -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAuthority; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; - -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -public class USubscription { - public static final UEntity SERVICE = UEntity.newBuilder() - .setName("core.usubscription") - .setVersionMajor(3) - .build(); - public static final String METHOD_SUBSCRIBE = "Subscribe"; - public static final String METHOD_UNSUBSCRIBE = "Unsubscribe"; - public static final String METHOD_FETCH_SUBSCRIPTIONS = "FetchSubscriptions"; - public static final String METHOD_CREATE_TOPIC = "CreateTopic"; - public static final String METHOD_DEPRECATE_TOPIC = "DeprecateTopic"; - public static final String METHOD_REGISTER_FOR_NOTIFICATIONS = "RegisterForNotifications"; - public static final String METHOD_UNREGISTER_FOR_NOTIFICATIONS = "UnregisterForNotifications"; - public static final String METHOD_FETCH_SUBSCRIBERS = "FetchSubscribers"; - public static final String METHOD_RESET = "Reset"; - - private static final CallOptions DEFAULT_OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(10_000) - .build(); - - private USubscription() {} - - public static Stub newStub(RpcClient proxy) { - return newStub(proxy, null, DEFAULT_OPTIONS); - } - - public static Stub newStub(RpcClient proxy, CallOptions options) { - return newStub(proxy, null, options); - } - - public static Stub newStub(RpcClient proxy, UAuthority authority, CallOptions options) { - return new Stub(proxy, authority, options); - } - - public static class Stub { - private final RpcClient proxy; - private final UAuthority authority; - private final CallOptions options; - - private Stub(RpcClient proxy, UAuthority authority, CallOptions options) { - this.proxy = proxy; - this.authority = authority; - this.options = options; - } - - private UUri buildUri(String method) { - final UUri.Builder builder = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest(method)); - if (authority != null) { - builder.setAuthority(authority); - } - return builder.build(); - } - - public Optional getAuthority() { - return (authority != null) ? Optional.of(authority) : Optional.empty(); - } - - public CallOptions getOptions() { - return options; - } - - public CompletionStage subscribe(SubscriptionRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_SUBSCRIBE), packToAny(request), options), SubscriptionResponse.class); - } - - public CompletionStage unsubscribe(UnsubscribeRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_UNSUBSCRIBE), packToAny(request), options), UStatus.class); - } - - public CompletionStage fetchSubscriptions(FetchSubscriptionsRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_FETCH_SUBSCRIPTIONS), packToAny(request), options), FetchSubscriptionsResponse.class); - } - - public CompletionStage createTopic(CreateTopicRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_CREATE_TOPIC), packToAny(request), options), UStatus.class); - } - - public CompletionStage deprecateTopic(DeprecateTopicRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_DEPRECATE_TOPIC), packToAny(request), options), UStatus.class); - } - - public CompletionStage registerForNotifications(NotificationsRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_REGISTER_FOR_NOTIFICATIONS), packToAny(request), options), UStatus.class); - } - - public CompletionStage unregisterForNotifications(NotificationsRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_UNREGISTER_FOR_NOTIFICATIONS), packToAny(request), options), UStatus.class); - } - - public CompletionStage fetchSubscribers(FetchSubscribersRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_FETCH_SUBSCRIBERS), packToAny(request), options), FetchSubscribersResponse.class); - } - - public CompletionStage reset(ResetRequest request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_RESET), packToAny(request), options), UStatus.class); - } - } -} diff --git a/library/src/main/java/org/eclipse/uprotocol/core/utwin/v2/UTwin.java b/library/src/main/java/org/eclipse/uprotocol/core/utwin/v2/UTwin.java deleted file mode 100644 index b75f635..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/core/utwin/v2/UTwin.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.utwin.v2; - -import static org.eclipse.uprotocol.rpc.RpcMapper.mapResponse; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; - -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAuthority; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.v1.UUriBatch; - -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -public class UTwin { - public static final UEntity SERVICE = UEntity.newBuilder() - .setName("core.utwin") - .setVersionMajor(2) - .build(); - public static final String METHOD_GET_LAST_MESSAGES = "GetLastMessages"; - public static final String METHOD_SET_LAST_MESSAGE = "SetLastMessage"; - - private static final CallOptions DEFAULT_OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(10_000) - .build(); - - private UTwin() {} - - public static UTwin.Stub newStub(RpcClient proxy) { - return newStub(proxy, null, DEFAULT_OPTIONS); - } - - public static UTwin.Stub newStub(RpcClient proxy, CallOptions options) { - return newStub(proxy, null, options); - } - - public static UTwin.Stub newStub(RpcClient proxy, UAuthority authority, CallOptions options) { - return new UTwin.Stub(proxy, authority, options); - } - - public static class Stub { - private final RpcClient proxy; - private final UAuthority authority; - private final CallOptions options; - - private Stub(RpcClient proxy, UAuthority authority, CallOptions options) { - this.proxy = proxy; - this.authority = authority; - this.options = options; - } - - private UUri buildUri(String method) { - final UUri.Builder builder = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest(method)); - if (authority != null) { - builder.setAuthority(authority); - } - return builder.build(); - } - - public Optional getAuthority() { - return (authority != null) ? Optional.of(authority) : Optional.empty(); - } - - public CallOptions getOptions() { - return options; - } - - public CompletionStage getLastMessages(UUriBatch request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_GET_LAST_MESSAGES), packToAny(request), options), GetLastMessagesResponse.class); - } - - public CompletionStage setLastMessage(UMessage request) { - return mapResponse(proxy.invokeMethod(buildUri(METHOD_SET_LAST_MESSAGE), packToAny(request), options), UStatus.class); - } - } -} diff --git a/library/src/main/java/org/eclipse/uprotocol/transport/UTransportAndroid.java b/library/src/main/java/org/eclipse/uprotocol/transport/UTransportAndroid.java new file mode 100644 index 0000000..76c35d4 --- /dev/null +++ b/library/src/main/java/org/eclipse/uprotocol/transport/UTransportAndroid.java @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2024 General Motors GTO LLC + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * SPDX-FileType: SOURCE + * SPDX-FileCopyrightText: 2023 General Motors GTO LLC + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.transport; + +import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; +import static org.eclipse.uprotocol.common.util.UStatusUtils.checkNotNull; +import static org.eclipse.uprotocol.common.util.UStatusUtils.isOk; +import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; +import static org.eclipse.uprotocol.common.util.log.Formatter.join; +import static org.eclipse.uprotocol.common.util.log.Formatter.stringify; +import static org.eclipse.uprotocol.common.util.log.Formatter.tag; +import static org.eclipse.uprotocol.uri.factory.UriFactory.WILDCARD_ENTITY_ID; +import static org.eclipse.uprotocol.uri.factory.UriFactory.WILDCARD_ENTITY_VERSION; +import static org.eclipse.uprotocol.uri.validator.UriValidator.matchesEntity; + +import static java.util.Optional.ofNullable; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Handler; +import android.util.Log; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import org.eclipse.uprotocol.common.util.log.Key; +import org.eclipse.uprotocol.core.ubus.ConnectionCallback; +import org.eclipse.uprotocol.core.ubus.UBusManager; +import org.eclipse.uprotocol.internal.HandlerExecutor; +import org.eclipse.uprotocol.uri.validator.UriFilter; +import org.eclipse.uprotocol.v1.UAttributes; +import org.eclipse.uprotocol.v1.UMessage; +import org.eclipse.uprotocol.v1.UStatus; +import org.eclipse.uprotocol.v1.UUri; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.stream.Stream; + +/** + * UTransport implementation based on Android Binder. + */ +public class UTransportAndroid implements UTransport { + /** + * The logging tag used by this class and all sub-components. + */ + public static final String TAG = "uTransport"; + + /** + * The permission necessary to connect to the uBus. + */ + public static final String PERMISSION_ACCESS_UBUS = "uprotocol.permission.ACCESS_UBUS"; + + /** + * The name of the meta-data element that must be present on an + * application or service element in a manifest to specify + * the entity id. + */ + public static final String META_DATA_ENTITY_ID = "uprotocol.entity.id"; + + /** + * The name of the meta-data element that must be present on an + * application or service element in a manifest to specify + * the entity major version. + */ + public static final String META_DATA_ENTITY_VERSION = "uprotocol.entity.version"; + + private final UUri mSource; + private final UBusManager mUBusManager; + private final Executor mCallbackExecutor; + + private final Object mRegistrationLock = new Object(); + @GuardedBy("mRegistrationLock") + private final Map> mListeners = new HashMap<>(); + @GuardedBy("mRegistrationLock") + private boolean mRegistrationExpired; + + private final String mTag; + private boolean mVerboseLoggable; + + private final ConnectionCallback mConnectionCallback = new ConnectionCallback() { + @Override + public void onConnected() { + synchronized (mRegistrationLock) { + if (mRegistrationExpired) { + mListeners.keySet().forEach(mUBusManager::enableDispatching); + mRegistrationExpired = false; + } + } + } + + @Override + public void onDisconnected() { + synchronized (mRegistrationLock) { + mListeners.clear(); + mRegistrationExpired = false; + } + } + + @Override + public void onConnectionInterrupted() { + synchronized (mRegistrationLock) { + mRegistrationExpired = true; + } + } + }; + + private final UListener mListener = this::handleMessage; + + @VisibleForTesting + UTransportAndroid(@NonNull Context context, UUri source, UBusManager manager, Executor executor) { + checkNonNullContext(context); + mSource = checkSource(getPackageInfo(context), source); + mUBusManager = ofNullable(manager).orElse(new UBusManager(context, mSource, mConnectionCallback, mListener)); + mCallbackExecutor = ofNullable(executor).orElse(context.getMainExecutor()); + + mTag = tag(TAG, Integer.toHexString(mSource.getUeId())); + mVerboseLoggable = Log.isLoggable(mTag, Log.VERBOSE); + if (mVerboseLoggable) { + Log.v(mTag, join(Key.PACKAGE, BuildConfig.LIBRARY_PACKAGE_NAME, Key.VERSION, BuildConfig.VERSION_NAME)); + } + } + + private static void checkNonNullContext(Context context) { + checkNotNull(context, "Context is null"); + if (context instanceof ContextWrapper contextWrapper && contextWrapper.getBaseContext() == null) { + throw new NullPointerException("ContextWrapper with null base passed as Context"); + } + } + + private static @NonNull PackageInfo getPackageInfo(@NonNull Context context) { + try { + return context.getPackageManager().getPackageInfo(context.getPackageName(), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + throw new SecurityException(e.getMessage(), e); + } + } + + private static @NonNull UUri checkSource(@NonNull PackageInfo packageInfo, UUri source) { + return Stream.concat(Stream.of(packageInfo.applicationInfo), + (packageInfo.services != null) ? Stream.of(packageInfo.services) : Stream.empty()) + .filter(Objects::nonNull) + .map(info -> { + final UUri foundSource = getSource(info); + if (source != null && foundSource != null) { + return matchesEntity(source, foundSource) ? foundSource : null; + } else { + return foundSource; + } + }) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new SecurityException("Missing or not matching '" + + META_DATA_ENTITY_ID + "' or '" + META_DATA_ENTITY_VERSION + "' meta-data in manifest")); + } + + private static UUri getSource(@NonNull PackageItemInfo info) { + if (info.metaData != null) { + final int id = info.metaData.getInt(META_DATA_ENTITY_ID); + final int version = info.metaData.getInt(META_DATA_ENTITY_VERSION); + if (id > 0 && id != WILDCARD_ENTITY_ID && version > 0 && version != WILDCARD_ENTITY_VERSION) { + final UUri.Builder builder = UUri.newBuilder() + .setUeId(id) + .setUeVersionMajor(version); + return builder.build(); + } + } + return null; + } + + /** + * Create an instance. + * + * @param context An application {@link Context}. This should not be null. If you are passing + * {@link ContextWrapper}, make sure that its base Context is non-null as well. + * Otherwise it will throw {@link NullPointerException}. + * @param handler A {@link Handler} on which callbacks should execute, or null to execute on + * the application's main thread. + * @return A {@link UTransportAndroid} instance. + * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_ID} and + * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. + */ + public static @NonNull UTransportAndroid create(@NonNull Context context, Handler handler) { + return new UTransportAndroid(context, null, null, new HandlerExecutor(handler)); + } + + /** + * Create an instance for a specified source. + * + * @param context An application {@link Context}. This should not be null. If you are passing + * {@link ContextWrapper}, make sure that its base Context is non-null as well. + * Otherwise it will throw {@link NullPointerException}. + * @param source A {@link UUri} containing entity id and major version, or null to use the + * first found declaration under application or service element + * in a manifest. + * @param handler A {@link Handler} on which callbacks should execute, or null to execute on + * the application's main thread. + * @return A {@link UTransportAndroid} instance. + * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_ID} and + * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. + */ + public static @NonNull UTransportAndroid create(@NonNull Context context, UUri source, Handler handler) { + return new UTransportAndroid(context, source, null, new HandlerExecutor(handler)); + } + + /** + * Create an instance. + * + * @param context An application {@link Context}. This should not be null. If you are passing + * {@link ContextWrapper}, make sure that its base Context is non-null as well. + * Otherwise it will throw {@link NullPointerException}. + * @param executor An {@link Executor} on which callbacks should execute, or null to execute on + * the application's main thread. + * @return A {@link UTransportAndroid} instance. + * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_ID} and + * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. + */ + public static @NonNull UTransportAndroid create(@NonNull Context context, Executor executor) { + return new UTransportAndroid(context, null, null, executor); + } + + /** + * Create an instance for a specified source. + * + * @param context An application {@link Context}. This should not be null. If you are passing + * {@link ContextWrapper}, make sure that its base Context is non-null as well. + * Otherwise it will throw {@link NullPointerException}. + * @param source A {@link UUri} containing entity id and major version, or null to use the + * first found declaration under application or service element + * in a manifest. + * @param executor An {@link Executor} on which callbacks should execute, or null to execute on + * the application's main thread. + * @return A {@link UTransportAndroid} instance. + * @throws SecurityException If the caller does not have {@link #META_DATA_ENTITY_ID} and + * {@link #META_DATA_ENTITY_VERSION} meta-data elements declared in the manifest. + */ + public static @NonNull UTransportAndroid create(@NonNull Context context, UUri source, Executor executor) { + return new UTransportAndroid(context, source, null, executor); + } + + /** + * Check whether the connection to the uBus is opened or not. This will return false + * if the connection is still in progress. + * + * @return true if is is connected. + */ + public boolean isOpened() { + return mUBusManager.isConnected(); + } + + /** + * Open the connection to the uBus. + * + *

Requires {@link #PERMISSION_ACCESS_UBUS} permission to access this API. + * + *

An instance connected with this method should be disconnected from the uBus by calling + * {@link #close()} before the passed {@link Context} is released. + * + * @return A {@link CompletionStage} used by a caller to receive the connection status. + */ + @Override + public @NonNull CompletionStage open() { + return mUBusManager.connect(); + } + + /** + * Close the connection to the uBus. + * + *

All previously registered listeners will be automatically unregistered. + */ + @Override + public void close() { + mUBusManager.disconnect().toCompletableFuture().join(); + } + + /** + * Get the source address of the entity. + * + * @return {@link UUri} representing the source address. + */ + @Override + public UUri getSource() { + return mSource; + } + + @VisibleForTesting + @NonNull String getTag() { + return mTag; + } + + @VisibleForTesting + void setLoggable(int level) { + mVerboseLoggable = level <= Log.VERBOSE; + } + + @VisibleForTesting + ConnectionCallback getConnectionCallback() { + return mConnectionCallback; + } + + @VisibleForTesting + UListener getListener() { + return mListener; + } + + /** + * Send a message. + * + * @param message A {@link UMessage} to be sent. + * @return A {@link CompletionStage} which provides a result code and other details upon completion. + */ + @Override + public CompletionStage send(UMessage message) { + return CompletableFuture.supplyAsync(() -> mUBusManager.send(message)); + } + + /** + * Register a listener to receive messages. + * + * @param sourceFilter The source address filter. + * @param sinkFilter The sink address filter. + * @param listener The {@link UListener} that will be executed when the message matching filters is received. + * @return A {@link CompletionStage} which provides a result code and other details upon completion. + */ + @Override + public CompletionStage registerListener(UUri sourceFilter, UUri sinkFilter, UListener listener) { + return CompletableFuture.supplyAsync(() -> { + try { + checkNotNull(listener, "Listener is null"); + final UriFilter filter = new UriFilter(sourceFilter, sinkFilter); + synchronized (mRegistrationLock) { + final Set listeners = ofNullable(mListeners.get(filter)).orElse(new HashSet<>()); + if (listeners.isEmpty()) { + final UStatus status = mUBusManager.enableDispatching(filter); + if (!isOk(status)) { + return status; + } + mListeners.put(filter, listeners); + } + listeners.add(listener); + return STATUS_OK; + } + } catch (Exception e) { + return toStatus(e); + } + }); + } + + /** + * Unregister a listener. + * + * @param sourceFilter The source address filter. + * @param sinkFilter The sink address filter. + * @param listener The {@link UListener} to be unregistered. + * @return A {@link CompletionStage} which provides a result code and other details upon completion. + */ + @Override + public CompletionStage unregisterListener(UUri sourceFilter, UUri sinkFilter, UListener listener) { + return CompletableFuture.supplyAsync(() -> { + try { + checkNotNull(listener, "Listener is null"); + final UriFilter filter = new UriFilter(sourceFilter, sinkFilter); + synchronized (mRegistrationLock) { + final Set listeners = mListeners.get(filter); + if (listeners != null && listeners.contains(listener)) { + listeners.remove(listener); + if (listeners.isEmpty()) { + mUBusManager.disableDispatchingQuietly(filter); + mListeners.remove(filter); + } + } + } + return STATUS_OK; + } catch (Exception e) { + return toStatus(e); + } + }); + } + + private void handleMessage(@NonNull UMessage message) { + if (mVerboseLoggable) { + Log.v(mTag, join(Key.EVENT, "Message received", Key.MESSAGE, stringify(message))); + } + mCallbackExecutor.execute(() -> { + final Set matchedListeners = new HashSet<>(); + final UAttributes attributes = message.getAttributes(); + synchronized (mRegistrationLock) { + mListeners.forEach((filter, listeners) -> { + if (filter.matches(attributes)) { + matchedListeners.addAll(listeners); + } + }); + } + matchedListeners.forEach(listener -> { + try { + listener.onReceive(message); + } catch (Exception e) { + Log.e(mTag, join(Key.FAILURE, "Listener failed", Key.REASON, e.getMessage())); + } + }); + }); + } +} diff --git a/library/src/main/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.java b/library/src/main/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.java deleted file mode 100644 index f30c5ca..0000000 --- a/library/src/main/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.v1.internal; - -import android.os.Parcel; - -import androidx.annotation.NonNull; - -import com.google.protobuf.InvalidProtocolBufferException; - -import org.eclipse.uprotocol.v1.UEntity; - -/** - * A parcelable wrapper for {@link UEntity}. - */ -public final class ParcelableUEntity extends ParcelableMessage { - - public static final Creator CREATOR = new Creator<>() { - public ParcelableUEntity createFromParcel(Parcel in) { - return new ParcelableUEntity(in); - } - - public ParcelableUEntity[] newArray(int size) { - return new ParcelableUEntity[size]; - } - }; - - private ParcelableUEntity(@NonNull Parcel in) { - super(in); - } - - public ParcelableUEntity(@NonNull UEntity entity) { - super(entity); - } - - @Override - protected @NonNull UEntity parse(@NonNull byte[] data) throws InvalidProtocolBufferException { - return UEntity.parseFrom(data); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/TestBase.java b/library/src/test/java/org/eclipse/uprotocol/TestBase.java index 5cb3d1f..1c8ae22 100644 --- a/library/src/test/java/org/eclipse/uprotocol/TestBase.java +++ b/library/src/test/java/org/eclipse/uprotocol/TestBase.java @@ -23,11 +23,10 @@ */ package org.eclipse.uprotocol; -import static org.eclipse.uprotocol.UPClient.META_DATA_ENTITY_ID; -import static org.eclipse.uprotocol.UPClient.META_DATA_ENTITY_NAME; -import static org.eclipse.uprotocol.UPClient.META_DATA_ENTITY_VERSION; import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; +import static org.eclipse.uprotocol.communication.UPayload.packToAny; +import static org.eclipse.uprotocol.transport.UTransportAndroid.META_DATA_ENTITY_ID; +import static org.eclipse.uprotocol.transport.UTransportAndroid.META_DATA_ENTITY_VERSION; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -47,110 +46,55 @@ import com.google.protobuf.Empty; -import org.eclipse.uprotocol.common.UStatusException; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; +import org.eclipse.uprotocol.communication.CallOptions; +import org.eclipse.uprotocol.communication.UPayload; +import org.eclipse.uprotocol.communication.UStatusException; import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UAuthority; import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UMessageType; -import org.eclipse.uprotocol.v1.UPayload; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UResource; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUID; import org.eclipse.uprotocol.v1.UUri; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @SuppressWarnings("SameParameterValue") public class TestBase { - protected static final UAuthority AUTHORITY_REMOTE = UAuthority.newBuilder() - .setName("cloud") - .build(); - protected static final UEntity SERVICE = UEntity.newBuilder() - .setName("test.service") - .setVersionMajor(1) - .build(); - protected static final UEntity CLIENT = UEntity.newBuilder() - .setName("test.client") - .setVersionMajor(1) - .build(); - protected static final UResource RESOURCE = UResource.newBuilder() - .setName("resource") - .setInstance("main") - .setMessage("State") - .build(); - protected static final UResource RESOURCE2 = UResource.newBuilder() - .setName("resource2") - .setInstance("main2") - .setMessage("State2") - .build(); - protected static final UUri RESOURCE_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(RESOURCE) - .build(); - protected static final UUri RESOURCE2_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(RESOURCE2) - .build(); - protected static final UUri METHOD_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest("method")) + protected static final String AUTHORITY_REMOTE = "cloud"; + protected static final int VERSION = 1; + protected static final int CLIENT_ID = 0x50; + protected static final int SERVICE_ID = 0x51; + protected static final int METHOD_ID = 0x1; + protected static final int RESOURCE_ID = 0x8000; + protected static final int RESOURCE2_ID = 0x8001; + protected static final UUri CLIENT_URI = UUri.newBuilder() + .setUeId(CLIENT_ID) + .setUeVersionMajor(VERSION) .build(); - protected static final UUri METHOD2_URI = UUri.newBuilder() - .setEntity(SERVICE) - .setResource(UResourceBuilder.forRpcRequest("method2")) + protected static final UUri SERVICE_URI = UUri.newBuilder() + .setUeId(SERVICE_ID) + .setUeVersionMajor(VERSION) .build(); - protected static final UUri RESPONSE_URI = UUri.newBuilder() - .setEntity(CLIENT) - .setResource(UResourceBuilder.forRpcResponse()) + protected static final UUri METHOD_URI = UUri.newBuilder(SERVICE_URI) + .setResourceId(METHOD_ID) .build(); - protected static final UUri RESOURCE_URI_REMOTE = UUri.newBuilder() - .setAuthority(AUTHORITY_REMOTE) - .setEntity(SERVICE) - .setResource(RESOURCE) + protected static final UUri RESOURCE_URI = UUri.newBuilder(SERVICE_URI) + .setResourceId(RESOURCE_ID) .build(); - protected static final UUri CLIENT_URI = UUri.newBuilder() - .setEntity(CLIENT) + protected static final UUri RESOURCE2_URI = UUri.newBuilder(SERVICE_URI) + .setResourceId(RESOURCE2_ID) .build(); - protected static final UUri SERVICE_URI = UUri.newBuilder() - .setEntity(SERVICE) + protected static final UUri RESOURCE_URI_REMOTE = UUri.newBuilder(RESOURCE_URI) + .setAuthorityName(AUTHORITY_REMOTE) .build(); + protected static final UUID ID = createId(); - protected static final UUID ID2 = createId(); - protected static final int TTL = 1000; + protected static final int TTL = CallOptions.DEFAULT.timeout(); protected static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG" + "4gU21pdGgiLCJpYXQiOjE1MTYyMzkwMjJ9.Q_w2AVguPRU2KskCXwR7ZHl09TQXEntfEA8Jj2_Jyew"; - protected static final CallOptions OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(TTL) - .setToken(TOKEN) - .build(); - protected static final CallOptions DEFAULT_OPTIONS = CallOptions.newBuilder() - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(10_000) - .build(); - protected static final UAttributes ATTRIBUTES = UAttributes.newBuilder() - .setId(ID) - .setType(UMessageType.UMESSAGE_TYPE_RESPONSE) - .setSource(METHOD_URI) - .setSink(RESPONSE_URI) - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(TTL) - .setPermissionLevel(5) - .setCommstatus(UCode.DEADLINE_EXCEEDED) - .setReqid(ID2) - .setToken(TOKEN) - .build(); protected static final UPayload PAYLOAD = packToAny(Empty.getDefaultInstance()); protected static final long DELAY_MS = 100; @@ -158,57 +102,17 @@ public class TestBase { return UuidFactory.Factories.UPROTOCOL.factory().create(); } - protected static @NonNull UAttributes buildPublishAttributes(@NonNull UUri source) { - return newPublishAttributesBuilder(source).build(); - } - - protected static @NonNull UAttributes buildRequestAttributes(@NonNull UUri responseUri, @NonNull UUri methodUri) { - return newRequestAttributesBuilder(responseUri, methodUri).build(); - } - - protected static @NonNull UAttributes buildResponseAttributes( - @NonNull UUri methodUri, @NonNull UUri responseUri, @NonNull UUID requestId) { - return newResponseAttributesBuilder(methodUri, responseUri, requestId).build(); - } - - protected static @NonNull UAttributesBuilder newPublishAttributesBuilder(@NonNull UUri source) { - return UAttributesBuilder.publish(source, UPriority.UPRIORITY_CS0); - } - - protected static @NonNull UAttributesBuilder newNotificationAttributesBuilder( - @NonNull UUri source, @NonNull UUri sink) { - return UAttributesBuilder.notification(source, sink, UPriority.UPRIORITY_CS0); - } - - protected static @NonNull UAttributesBuilder newRequestAttributesBuilder( - @NonNull UUri responseUri, @NonNull UUri methodUri) { - return UAttributesBuilder.request(responseUri, methodUri, UPriority.UPRIORITY_CS4, TTL); - } - - protected static @NonNull UAttributesBuilder newResponseAttributesBuilder( - @NonNull UUri methodUri, @NonNull UUri responseUri, @NonNull UUID requestId) { - return UAttributesBuilder.response(methodUri, responseUri, UPriority.UPRIORITY_CS4, requestId); - } - - protected static @NonNull UMessage buildMessage(UPayload payload, UAttributes attributes) { - final UMessage.Builder builder = UMessage.newBuilder(); - if (payload != null) { - builder.setPayload(payload); - } - if (attributes != null) { - builder.setAttributes(attributes); - } - return builder.build(); + protected static @NonNull Bundle buildEntityMetadata(UUri uri) { + return buildEntityMetadata(uri.getUeId(), uri.getUeVersionMajor()); } - protected static @NonNull Bundle buildMetadata(@NonNull UEntity entity) { + protected static @NonNull Bundle buildEntityMetadata(int id, int version) { final Bundle bundle = new Bundle(); - bundle.putString(META_DATA_ENTITY_NAME, entity.getName()); - if (entity.hasVersionMajor()) { - bundle.putInt(META_DATA_ENTITY_VERSION, entity.getVersionMajor()); + if (id > 0) { + bundle.putInt(META_DATA_ENTITY_ID, id); } - if (entity.hasId()) { - bundle.putInt(META_DATA_ENTITY_ID, entity.getId()); + if (version > 0) { + bundle.putInt(META_DATA_ENTITY_VERSION, version); } return bundle; } @@ -283,13 +187,13 @@ protected static void assertStatus(@NonNull UCode code, @NonNull UStatus status) assertEquals(code, status.getCode()); } - protected static T getOrThrow(@NonNull Future future) { - return getOrThrow(future, DELAY_MS); + protected static T getOrThrow(@NonNull CompletionStage stage) { + return getOrThrow(stage, DELAY_MS); } - protected static T getOrThrow(@NonNull Future future, long timeout) { + protected static T getOrThrow(@NonNull CompletionStage stage, long timeout) { try { - return future.get(timeout, TimeUnit.MILLISECONDS); + return stage.toCompletableFuture().get(timeout, TimeUnit.MILLISECONDS); } catch (Exception e) { throw new UStatusException(toStatus(e)); } diff --git a/library/src/test/java/org/eclipse/uprotocol/UPClientTest.java b/library/src/test/java/org/eclipse/uprotocol/UPClientTest.java deleted file mode 100644 index 240d006..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/UPClientTest.java +++ /dev/null @@ -1,843 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol; - -import static org.eclipse.uprotocol.client.BuildConfig.LIBRARY_PACKAGE_NAME; -import static org.eclipse.uprotocol.client.BuildConfig.VERSION_NAME; -import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; -import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; -import static org.eclipse.uprotocol.common.util.UStatusUtils.toStatus; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.ComponentName; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.protobuf.Int32Value; - -import org.eclipse.uprotocol.UPClient.ServiceLifecycleListener; -import org.eclipse.uprotocol.core.ubus.UBusManager; -import org.eclipse.uprotocol.transport.UListener; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.v1.CallOptions; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UMessageType; -import org.eclipse.uprotocol.v1.UPayload; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.validation.ValidationResult; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.MockedStatic; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; -import org.robolectric.shadows.ShadowLog; -import org.robolectric.shadows.ShadowPackageManager; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -@RunWith(AndroidJUnit4.class) -public class UPClientTest extends TestBase { - private static final UMessage MESSAGE = buildMessage(PAYLOAD, buildPublishAttributes(RESOURCE_URI)); - private static final UPayload REQUEST_PAYLOAD = packToAny(Int32Value.newBuilder().setValue(1).build()); - private static final UPayload RESPONSE_PAYLOAD = packToAny(STATUS_OK); - - private Context mContext; - private String mPackageName; - private ShadowPackageManager mShadowPackageManager; - private Handler mHandler; - private Executor mExecutor; - private ServiceLifecycleListener mServiceLifecycleListener; - private UListener mListener; - private UListener mListener2; - private UBusManager mManager; - private UPClient mClient; - - @Before - public void setUp() { - mContext = RuntimeEnvironment.getApplication(); - mPackageName = mContext.getPackageName(); - mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); - mHandler = newMockHandler(); - mExecutor = newMockExecutor(); - mServiceLifecycleListener = mock(ServiceLifecycleListener.class); - mListener = mock(UListener.class); - mListener2 = mock(UListener.class); - mManager = mock(UBusManager.class); - injectPackage(buildPackageInfo(mPackageName, buildMetadata(CLIENT))); - mClient = new UPClient(mContext, CLIENT, mManager, mExecutor, mServiceLifecycleListener); - mClient.setLoggable(Log.INFO); - } - - private void injectPackage(@NonNull PackageInfo packageInfo) { - mShadowPackageManager.installPackage(packageInfo); - } - - private static void redirectMessages(@NonNull UBusManager manager, @NonNull UPClient client) { - doAnswer(invocation -> { - client.getListener().onReceive(invocation.getArgument(0)); - return STATUS_OK; - }).when(manager).send(any()); - } - - @Test - public void testConstants() { - assertEquals("uprotocol.permission.ACCESS_UBUS", UPClient.PERMISSION_ACCESS_UBUS); - assertEquals("uprotocol.entity.name", UPClient.META_DATA_ENTITY_NAME); - assertEquals("uprotocol.entity.version", UPClient.META_DATA_ENTITY_VERSION); - } - - @Test - public void testCreate() { - injectPackage(buildPackageInfo(mPackageName, - buildServiceInfo(new ComponentName(mPackageName, ".Service"), buildMetadata(SERVICE)))); - assertNotNull(UPClient.create(mContext, SERVICE, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateWithoutEntity() { - assertNotNull(UPClient.create(mContext, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateWithEntityId() { - final UEntity entity = UEntity.newBuilder() - .setName(CLIENT.getName()) - .setVersionMajor(CLIENT.getVersionMajor()) - .setId(100) - .build(); - injectPackage(buildPackageInfo(mPackageName, buildMetadata(entity))); - assertNotNull(UPClient.create(mContext, entity, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateWitHandler() { - assertNotNull(UPClient.create(mContext, mHandler, mServiceLifecycleListener)); - } - - @Test - public void testCreateWithDefaultCallbackThread() { - assertNotNull(UPClient.create(mContext, (Handler) null, mServiceLifecycleListener)); - assertNotNull(UPClient.create(mContext, (Executor) null, mServiceLifecycleListener)); - } - - @Test - public void testCreateWithoutServiceLifecycleListener() { - assertNotNull(UPClient.create(mContext, mExecutor, null)); - } - - @Test - public void testCreateWithBadContextWrapper() { - final ContextWrapper context = spy(new ContextWrapper(mContext)); - doReturn(null).when(context).getBaseContext(); - assertThrows(NullPointerException.class, () -> UPClient.create(context, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreatePackageManagerNotAvailable() { - assertThrows(NullPointerException.class, () -> UPClient.create(mock(Context.class), mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreatePackageNotFound() throws NameNotFoundException { - final PackageManager manager = mock(PackageManager.class); - doThrow(new NameNotFoundException()).when(manager).getPackageInfo(anyString(), anyInt()); - final Context context = spy(new ContextWrapper(mContext)); - doReturn(manager).when(context).getPackageManager(); - assertThrows(SecurityException.class, () -> UPClient.create(context, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateEntityNotDeclared() { - injectPackage(buildPackageInfo(mPackageName)); - assertThrows(SecurityException.class, () -> UPClient.create(mContext, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateEntityNameNotDeclared() { - injectPackage(buildPackageInfo(mPackageName, - buildMetadata(UEntity.newBuilder().setVersionMajor(1).build()))); - assertThrows(SecurityException.class, () -> UPClient.create(mContext, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateEntityVersionNotDeclared() { - injectPackage(buildPackageInfo(mPackageName, - buildMetadata(UEntity.newBuilder().setName(CLIENT.getName()).build()))); - assertThrows(SecurityException.class, () -> UPClient.create(mContext, mExecutor, mServiceLifecycleListener)); - } - - @Test - public void testCreateVerboseVersionLogged() { - final String tag = mClient.getTag(); - ShadowLog.setLoggable(tag, Log.VERBOSE); - assertNotNull(UPClient.create(mContext, mClient.getEntity(), mHandler, mServiceLifecycleListener)); - ShadowLog.getLogsForTag(tag).stream() - .filter(it -> it.msg.contains(LIBRARY_PACKAGE_NAME) && it.msg.contains(VERSION_NAME)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Version is not printed")); - } - - @Test - public void testConnect() { - final CompletableFuture future = new CompletableFuture<>(); - doReturn(future).when(mManager).connect(); - assertEquals(future, mClient.connect()); - } - - @Test - public void testDisconnect() { - final CompletableFuture future = new CompletableFuture<>(); - doReturn(future).when(mManager).disconnect(); - assertEquals(future, mClient.disconnect()); - } - - @Test - public void testIsDisconnected() { - assertFalse(mClient.isDisconnected()); - doReturn(true).when(mManager).isDisconnected(); - assertTrue(mClient.isDisconnected()); - } - - @Test - public void testIsConnecting() { - assertFalse(mClient.isConnecting()); - doReturn(true).when(mManager).isConnecting(); - assertTrue(mClient.isConnecting()); - } - - @Test - public void testIsConnected() { - assertFalse(mClient.isConnected()); - doReturn(true).when(mManager).isConnected(); - assertTrue(mClient.isConnected()); - } - - @Test - public void testOnConnected() { - mClient.getConnectionCallback().onConnected(); - verify(mServiceLifecycleListener, times(1)).onLifecycleChanged(mClient, true); - } - - @Test - public void testOnDisconnected() { - mClient.getConnectionCallback().onDisconnected(); - verify(mServiceLifecycleListener, times(1)).onLifecycleChanged(mClient, false); - } - - @Test - public void testOnConnectionInterrupted() { - mClient.getConnectionCallback().onConnectionInterrupted(); - verify(mServiceLifecycleListener, times(1)).onLifecycleChanged(mClient, false); - } - - @Test - public void testOnConnectedSuppressed() { - final UPClient client = new UPClient(mContext, CLIENT, mManager, mExecutor, null); - client.getConnectionCallback().onConnected(); - verify(mExecutor, times(1)).execute(any()); - } - - @Test - public void testGetEntity() { - assertEquals(CLIENT, mClient.getEntity()); - } - - @Test - public void testGetUri() { - assertEquals(CLIENT, mClient.getUri().getEntity()); - } - - @Test - public void testSend() { - doReturn(STATUS_OK).when(mManager).send(MESSAGE); - assertStatus(UCode.OK, mClient.send(MESSAGE)); - } - - @Test - public void testRegisterGenericListener() { - doReturn(STATUS_OK).when(mManager).enableDispatching(RESOURCE_URI); - assertStatus(UCode.OK, mClient.registerListener(RESOURCE_URI, mListener)); - verify(mManager, times(1)).enableDispatching(RESOURCE_URI); - verify(mManager, never()).getLastMessage(RESOURCE_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testRegisterGenericListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(UUri.getDefaultInstance(), mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(null, mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(RESOURCE_URI, null)); - verify(mManager, never()).enableDispatching(RESOURCE_URI); - } - - @Test - public void testRegisterGenericListenerDifferentTopics() { - doReturn(STATUS_OK).when(mManager).enableDispatching(RESOURCE_URI); - doReturn(STATUS_OK).when(mManager).enableDispatching(RESOURCE2_URI); - assertStatus(UCode.OK, mClient.registerListener(RESOURCE_URI, mListener)); - assertStatus(UCode.OK, mClient.registerListener(RESOURCE2_URI, mListener)); - verify(mManager, times(1)).enableDispatching(RESOURCE_URI); - verify(mManager, times(1)).enableDispatching(RESOURCE2_URI); - } - - @Test - public void testRegisterGenericListenerSame() { - testRegisterGenericListener(); - assertStatus(UCode.OK, mClient.registerListener(RESOURCE_URI, mListener)); - verify(mManager, times(1)).enableDispatching(RESOURCE_URI); - verify(mManager, never()).getLastMessage(RESOURCE_URI); - } - - @Test - public void testRegisterGenericListenerNotFirst() { - testRegisterGenericListener(); - assertStatus(UCode.OK, mClient.registerListener(RESOURCE_URI, mListener2)); - verify(mManager, times(1)).enableDispatching(RESOURCE_URI); - verify(mManager, times(1)).getLastMessage(RESOURCE_URI); - } - - @Test - public void testRegisterGenericListenerNotFirstLastMessageNotified() { - doReturn(MESSAGE).when(mManager).getLastMessage(RESOURCE_URI); - testRegisterGenericListenerNotFirst(); - verify(mListener2, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); - } - - @Test - public void testRegisterGenericListenerFailed() { - doReturn(buildStatus(UCode.UNAUTHENTICATED)).when(mManager).enableDispatching(RESOURCE_URI); - assertStatus(UCode.UNAUTHENTICATED, mClient.registerListener(RESOURCE_URI, mListener)); - } - - @Test - public void testRegisterGenericListenerWhenReconnected() { - testRegisterGenericListener(); - mClient.getConnectionCallback().onConnectionInterrupted(); - verify(mManager, timeout(DELAY_MS).times(0)).disableDispatchingQuietly(RESOURCE_URI); - mClient.getConnectionCallback().onConnected(); - verify(mManager, timeout(DELAY_MS).times(2)).enableDispatching(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListener() { - testRegisterGenericListener(); - doReturn(STATUS_OK).when(mManager).disableDispatching(RESOURCE_URI); - assertStatus(UCode.OK, mClient.unregisterListener(RESOURCE_URI, mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterGenericListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(UUri.getDefaultInstance(), mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(null, mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(RESOURCE_URI, null)); - verify(mManager, never()).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListenerSame() { - testUnregisterGenericListener(); - assertStatus(UCode.OK, mClient.unregisterListener(RESOURCE_URI, mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListenerNotRegistered() { - testRegisterGenericListener(); - assertStatus(UCode.OK, mClient.unregisterListener(RESOURCE_URI, mListener2)); - verify(mManager, times(0)).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListenerNotLast() { - testRegisterGenericListenerNotFirst(); - assertStatus(UCode.OK, mClient.unregisterListener(RESOURCE_URI, mListener)); - verify(mManager, never()).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListenerLast() { - testUnregisterGenericListenerNotLast(); - assertStatus(UCode.OK, mClient.unregisterListener(RESOURCE_URI, mListener2)); - verify(mManager, times(1)).disableDispatchingQuietly(RESOURCE_URI); - } - - @Test - public void testUnregisterGenericListenerWhenDisconnected() { - testRegisterGenericListener(); - mClient.getConnectionCallback().onDisconnected(); - mClient.getListener().onReceive(MESSAGE); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(MESSAGE); - } - - @Test - public void testUnregisterGenericListenerFromAllTopics() { - testRegisterGenericListenerDifferentTopics(); - assertStatus(UCode.OK, mClient.unregisterListener(mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(RESOURCE_URI); - verify(mManager, times(1)).disableDispatchingQuietly(RESOURCE2_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterGenericListenerFromAllTopicsWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(null)); - } - - @Test - public void testOnReceiveGenericMessage() { - testRegisterGenericListenerNotFirst(); - mClient.getListener().onReceive(MESSAGE); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); - verify(mListener2, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); - } - - @Test - public void testOnReceiveGenericMessageNotRegistered() { - testUnregisterGenericListener(); - mClient.getListener().onReceive(MESSAGE); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(MESSAGE); - } - - @Test - public void testOnReceiveNotificationMessage() { - testRegisterGenericListener(); - final UMessage message = - buildMessage(PAYLOAD,newNotificationAttributesBuilder(RESOURCE_URI, CLIENT_URI).build()); - mClient.getListener().onReceive(message); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(message); - } - - @Test - public void testOnReceiveNotificationMessageWrongSink() { - mClient.setLoggable(Log.VERBOSE); - testRegisterGenericListener(); - final UMessage message = - buildMessage(PAYLOAD, newNotificationAttributesBuilder(RESOURCE_URI, SERVICE_URI).build()); - mClient.getListener().onReceive(message); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(message); - } - - @Test - public void testOnReceiveMessageExpired() { - mClient.setLoggable(Log.VERBOSE); - testRegisterGenericListener(); - final UMessage message = buildMessage(PAYLOAD, newPublishAttributesBuilder(RESOURCE_URI).withTtl(1).build()); - sleep(DELAY_MS); - mClient.getListener().onReceive(message); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(message); - } - - @Test - public void testOnReceiveMessageWithoutAttributes() { - testRegisterGenericListener(); - final UMessage message = buildMessage(null, null); - mClient.getListener().onReceive(message); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(message); - } - - @Test - public void testOnReceiveMessageWithUnknownType() { - testRegisterGenericListener(); - try (MockedStatic mockedValidator = mockStatic(UAttributesValidator.class)) { - final UMessage message = buildMessage(null, null); - final UAttributesValidator dummyValidator = new UAttributesValidator() { - @Override - public ValidationResult validate(UAttributes attributes) { - return ValidationResult.success(); - } - @Override - public ValidationResult validateType(UAttributes attributes) { - return ValidationResult.success(); - } - }; - mockedValidator.when(() -> UAttributesValidator.getValidator(message.getAttributes())) - .thenReturn(dummyValidator); - mClient.getListener().onReceive(message); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(message); - } - } - - @Test - public void testRegisterRequestListener() { - doReturn(STATUS_OK).when(mManager).enableDispatching(METHOD_URI); - assertStatus(UCode.OK, mClient.registerListener(METHOD_URI, mListener)); - verify(mManager, times(1)).enableDispatching(METHOD_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testRegisterRequestListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(UUri.getDefaultInstance(), mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(null, mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.registerListener(METHOD_URI, null)); - verify(mManager, never()).enableDispatching(METHOD_URI); - } - - @Test - public void testRegisterRequestListenerDifferentMethods() { - doReturn(STATUS_OK).when(mManager).enableDispatching(METHOD_URI); - doReturn(STATUS_OK).when(mManager).enableDispatching(METHOD2_URI); - assertStatus(UCode.OK, mClient.registerListener(METHOD_URI, mListener)); - assertStatus(UCode.OK, mClient.registerListener(METHOD2_URI, mListener)); - verify(mManager, times(1)).enableDispatching(METHOD_URI); - verify(mManager, times(1)).enableDispatching(METHOD2_URI); - } - - @Test - public void testRegisterRequestListenerSame() { - testRegisterRequestListener(); - assertStatus(UCode.OK, mClient.registerListener(METHOD_URI, mListener)); - verify(mManager, times(1)).enableDispatching(METHOD_URI); - } - - @Test - public void testRegisterRequestListenerNotFirst() { - testRegisterRequestListener(); - assertStatus(UCode.ALREADY_EXISTS, mClient.registerListener(METHOD_URI, mListener2)); - verify(mManager, times(1)).enableDispatching(METHOD_URI); - } - - @Test - public void testRegisterRequestListenerFailed() { - doReturn(buildStatus(UCode.UNAUTHENTICATED)).when(mManager).enableDispatching(METHOD_URI); - assertStatus(UCode.UNAUTHENTICATED, mClient.registerListener(METHOD_URI, mListener)); - } - - @Test - public void testRegisterRequestListenerWhenReconnected() { - testRegisterRequestListener(); - mClient.getConnectionCallback().onConnectionInterrupted(); - verify(mManager, timeout(DELAY_MS).times(0)).disableDispatchingQuietly(METHOD_URI); - mClient.getConnectionCallback().onConnected(); - verify(mManager, timeout(DELAY_MS).times(2)).enableDispatching(METHOD_URI); - } - - @Test - public void testUnregisterRequestListener() { - testRegisterRequestListener(); - doReturn(STATUS_OK).when(mManager).disableDispatching(METHOD_URI); - assertStatus(UCode.OK, mClient.unregisterListener(METHOD_URI, mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(METHOD_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterRequestListenerWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(UUri.getDefaultInstance(), mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(null, mListener)); - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(METHOD_URI, null)); - verify(mManager, never()).disableDispatchingQuietly(METHOD_URI); - } - - @Test - public void testUnregisterRequestListenerSame() { - testUnregisterRequestListener(); - assertStatus(UCode.OK, mClient.unregisterListener(METHOD_URI, mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(METHOD_URI); - } - - @Test - public void testUnregisterRequestListenerNotRegistered() { - testRegisterRequestListener(); - assertStatus(UCode.OK, mClient.unregisterListener(METHOD_URI, mListener2)); - verify(mManager, times(0)).disableDispatchingQuietly(METHOD_URI); - } - - @Test - public void testUnregisterRequestListenerWhenDisconnected() { - testRegisterRequestListener(); - mClient.getConnectionCallback().onDisconnected(); - final UMessage requestMessage = buildMessage(PAYLOAD, buildRequestAttributes(RESPONSE_URI, METHOD_URI)); - mClient.getListener().onReceive(requestMessage); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(eq(requestMessage)); - } - - @Test - public void testUnregisterRequestListenerFromAllMethods() { - testRegisterRequestListenerDifferentMethods(); - assertStatus(UCode.OK, mClient.unregisterListener(mListener)); - verify(mManager, times(1)).disableDispatchingQuietly(METHOD_URI); - verify(mManager, times(1)).disableDispatchingQuietly(METHOD2_URI); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testUnregisterRequestListenerFromAllMethodsWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, mClient.unregisterListener(null)); - } - - @Test - public void testUnregisterRequestListenerFromAllMethodsNotRegistered() { - testRegisterRequestListenerDifferentMethods(); - assertStatus(UCode.OK, mClient.unregisterListener(mListener2)); - verify(mManager, times(0)).disableDispatchingQuietly(METHOD_URI); - verify(mManager, times(0)).disableDispatchingQuietly(METHOD2_URI); - } - - @Test - public void testOnReceiveRequestMessage() { - testRegisterRequestListener(); - final UMessage requestMessage = buildMessage(PAYLOAD, buildRequestAttributes(RESPONSE_URI, METHOD_URI)); - mClient.getListener().onReceive(requestMessage); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(eq(requestMessage)); - } - - @Test - public void testOnReceiveRequestMessageNotRegistered() { - testUnregisterRequestListener(); - final UMessage requestMessage = buildMessage(PAYLOAD, buildRequestAttributes(RESPONSE_URI, METHOD_URI)); - mClient.getListener().onReceive(requestMessage); - verify(mListener, timeout(DELAY_MS).times(0)).onReceive(eq(requestMessage)); - } - - @Test - public void testInvokeMethod() throws Exception { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - final UAttributes requestAttributes = requestMessage.getAttributes(); - assertEquals(REQUEST_PAYLOAD, requestMessage.getPayload()); - assertEquals(RESPONSE_URI, requestAttributes.getSource()); - assertEquals(METHOD_URI, requestAttributes.getSink()); - assertEquals(OPTIONS.getPriority(), requestAttributes.getPriority()); - assertEquals(OPTIONS.getTtl(), requestAttributes.getTtl()); - assertEquals(OPTIONS.getToken(), requestAttributes.getToken()); - assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, requestAttributes.getType()); - mClient.send(buildMessage(RESPONSE_PAYLOAD, UAttributesBuilder.response(requestMessage.getAttributes()).build())); - - final UMessage responseMessage = responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS); - final UAttributes responseAttributes = responseMessage.getAttributes(); - assertEquals(RESPONSE_PAYLOAD, responseMessage.getPayload()); - assertEquals(METHOD_URI, responseAttributes.getSource()); - assertEquals(RESPONSE_URI, responseAttributes.getSink()); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, responseAttributes.getType()); - assertEquals(requestAttributes.getId(), responseAttributes.getReqid()); - } - - @Test - public void testInvokeMethodWithoutToken() throws Exception { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CallOptions options = CallOptions.newBuilder(OPTIONS).clearToken().build(); - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, options).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - final UAttributes requestAttributes = requestMessage.getAttributes(); - assertEquals(REQUEST_PAYLOAD, requestMessage.getPayload()); - assertEquals(RESPONSE_URI, requestAttributes.getSource()); - assertEquals(METHOD_URI, requestAttributes.getSink()); - assertEquals(options.getPriority(), requestAttributes.getPriority()); - assertEquals(options.getTtl(), requestAttributes.getTtl()); - assertEquals(options.getToken(), requestAttributes.getToken()); - assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, requestAttributes.getType()); - mClient.send(buildMessage(RESPONSE_PAYLOAD, UAttributesBuilder.response(requestMessage.getAttributes()).build())); - - final UMessage responseMessage = responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS); - final UAttributes responseAttributes = responseMessage.getAttributes(); - assertEquals(RESPONSE_PAYLOAD, responseMessage.getPayload()); - assertEquals(METHOD_URI, responseAttributes.getSource()); - assertEquals(RESPONSE_URI, responseAttributes.getSink()); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, responseAttributes.getType()); - assertEquals(requestAttributes.getId(), responseAttributes.getReqid()); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testInvokeMethodWithInvalidArgument() { - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(null, PAYLOAD, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(UUri.getDefaultInstance(), PAYLOAD, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(METHOD_URI, null, OPTIONS).toCompletableFuture().get()))); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(METHOD_URI, PAYLOAD, null).toCompletableFuture().get()))); - } - - @Test - public void testInvokeMethodWithInvalidPriority() { - final CallOptions options = CallOptions.newBuilder(OPTIONS).setPriority(UPriority.UPRIORITY_CS0).build(); - assertStatus(UCode.INVALID_ARGUMENT, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(METHOD_URI, PAYLOAD, options).toCompletableFuture().get()))); - } - - @Test - public void testInvokeMethodOtherResponseReceive() { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(any()); - final UMessage responseMessage = - buildMessage(PAYLOAD, buildResponseAttributes(METHOD_URI, RESPONSE_URI, createId())); - mClient.getListener().onReceive(responseMessage); - - assertThrows(TimeoutException.class, () -> responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS)); - assertFalse(responseFuture.isDone()); - } - - @Test - public void testInvokeMethodWhenDisconnected() { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(any()); - final UMessage responseMessage = - buildMessage(PAYLOAD, buildResponseAttributes(METHOD_URI, RESPONSE_URI, createId())); - mClient.getListener().onReceive(responseMessage); - - testOnDisconnected(); - assertStatus(UCode.CANCELLED, toStatus(assertThrows( - ExecutionException.class, () -> responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS)))); - } - - @Test - public void testInvokeMethodCompletedWithCommStatus() { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - mClient.send(buildMessage(null, UAttributesBuilder - .response(requestMessage.getAttributes()) - .withCommStatus(UCode.ABORTED) - .build())); - - assertStatus(UCode.ABORTED, toStatus(assertThrows( - ExecutionException.class, () -> responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS)))); - } - - @Test - public void testInvokeMethodCompletedWithCommStatusOk() throws Exception { - testRegisterRequestListener(); - redirectMessages(mManager, mClient); - - final CompletableFuture responseFuture = - mClient.invokeMethod(METHOD_URI, REQUEST_PAYLOAD, OPTIONS).toCompletableFuture(); - assertFalse(responseFuture.isDone()); - - final ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UMessage.class); - verify(mListener, timeout(DELAY_MS).times(1)).onReceive(requestCaptor.capture()); - final UMessage requestMessage = requestCaptor.getValue(); - final UAttributes requestAttributes = requestMessage.getAttributes(); - mClient.send(buildMessage(null, UAttributesBuilder - .response(requestMessage.getAttributes()) - .withCommStatus(UCode.OK) - .build())); - - final UMessage responseMessage = responseFuture.get(DELAY_MS, TimeUnit.MILLISECONDS); - final UAttributes responseAttributes = responseMessage.getAttributes(); - assertEquals(UPayload.getDefaultInstance(), responseMessage.getPayload()); - assertEquals(METHOD_URI, responseAttributes.getSource()); - assertEquals(RESPONSE_URI, responseAttributes.getSink()); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, responseAttributes.getType()); - assertEquals(requestAttributes.getId(), responseAttributes.getReqid()); - assertEquals(UCode.OK, responseAttributes.getCommstatus()); - } - - @Test - public void testInvokeMethodSameRequest() { - doReturn(buildStatus(UCode.OK)).when(mManager).send(any()); - final UAttributesBuilder builder = UAttributesBuilder.request(RESPONSE_URI, METHOD_URI, UPriority.UPRIORITY_CS4, TTL); - try (MockedStatic mockedBuilder = mockStatic(UAttributesBuilder.class)) { - mockedBuilder.when(() -> UAttributesBuilder.request(RESPONSE_URI, METHOD_URI, UPriority.UPRIORITY_CS4, TTL)) - .thenReturn(builder); - mClient.invokeMethod(METHOD_URI, PAYLOAD, OPTIONS); - assertStatus(UCode.ABORTED, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(METHOD_URI, PAYLOAD, OPTIONS).toCompletableFuture().get()))); - } - } - - @Test - public void testInvokeMethodSendFailure() { - doReturn(buildStatus(UCode.UNAVAILABLE)).when(mManager).send(any()); - assertStatus(UCode.UNAVAILABLE, toStatus(assertThrows(ExecutionException.class, - () -> mClient.invokeMethod(METHOD_URI, PAYLOAD, OPTIONS).toCompletableFuture().get()))); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/common/UStatusExceptionTest.java b/library/src/test/java/org/eclipse/uprotocol/common/UStatusExceptionTest.java deleted file mode 100644 index 447710d..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/common/UStatusExceptionTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.common; - -import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; -import static org.junit.Assert.assertEquals; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UStatus; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class UStatusExceptionTest extends TestBase { - private static final UCode CODE = UCode.OK; - private static final String MESSAGE = "Test message"; - private static final UStatus STATUS = UStatus.newBuilder().setCode(CODE).setMessage(MESSAGE).build(); - private static final Throwable CAUSE = new Throwable(MESSAGE); - - @Test - public void testConstructorWithStatus() { - final UStatusException exception = new UStatusException(STATUS); - assertEquals(STATUS, exception.getStatus()); - assertEquals(CODE, exception.getCode()); - assertEquals(MESSAGE, exception.getMessage()); - } - - @Test - public void testConstructorWithStatusAndCause() { - final UStatusException exception = new UStatusException(STATUS, CAUSE); - assertEquals(STATUS, exception.getStatus()); - assertEquals(CODE, exception.getCode()); - assertEquals(MESSAGE, exception.getMessage()); - assertEquals(CAUSE, exception.getCause()); - } - - @Test - public void testConstructorWithCodeAndMessage() { - final UStatusException exception = new UStatusException(CODE, MESSAGE); - assertEquals(STATUS, exception.getStatus()); - assertEquals(CODE, exception.getCode()); - assertEquals(MESSAGE, exception.getMessage()); - } - - @Test - public void testConstructorWithCodeMessageAndCause() { - final UStatusException exception = new UStatusException(CODE, MESSAGE, CAUSE); - assertEquals(STATUS, exception.getStatus()); - assertEquals(CODE, exception.getCode()); - assertEquals(MESSAGE, exception.getMessage()); - assertEquals(CAUSE, exception.getCause()); - } - - @Test - public void testConstructorNegative() { - final UStatusException exception = new UStatusException(null); - assertEquals(buildStatus(UCode.UNKNOWN), exception.getStatus()); - assertEquals(UCode.UNKNOWN, exception.getCode()); - assertEquals("", exception.getMessage()); - } - - @Test - public void testGetStatus() { - final UStatusException exception = new UStatusException(STATUS); - assertEquals(STATUS, exception.getStatus()); - } - - @Test - public void testGetCode() { - final UStatusException exception = new UStatusException(STATUS); - assertEquals(CODE, exception.getCode()); - } - - @Test - public void testGetMessage() { - final UStatusException exception = new UStatusException(STATUS); - assertEquals(MESSAGE, exception.getMessage()); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/common/util/UStatusUtilsTest.java b/library/src/test/java/org/eclipse/uprotocol/common/util/UStatusUtilsTest.java index a14b456..a4a6363 100644 --- a/library/src/test/java/org/eclipse/uprotocol/common/util/UStatusUtilsTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/common/util/UStatusUtilsTest.java @@ -35,7 +35,7 @@ import com.google.protobuf.InvalidProtocolBufferException; -import org.eclipse.uprotocol.common.UStatusException; +import org.eclipse.uprotocol.communication.UStatusException; import org.eclipse.uprotocol.v1.UCode; import org.eclipse.uprotocol.v1.UStatus; import org.junit.Test; diff --git a/library/src/test/java/org/eclipse/uprotocol/common/util/log/FormatterTest.java b/library/src/test/java/org/eclipse/uprotocol/common/util/log/FormatterTest.java index 7030f4e..a5f8b22 100644 --- a/library/src/test/java/org/eclipse/uprotocol/common/util/log/FormatterTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/common/util/log/FormatterTest.java @@ -25,20 +25,18 @@ import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; -import static org.eclipse.uprotocol.common.util.UStatusUtils.getCode; -import static org.eclipse.uprotocol.common.util.log.Formatter.SEPARATOR_PAIR; -import static org.eclipse.uprotocol.common.util.log.Formatter.SEPARATOR_PAIRS; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.notification; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.publish; +import static org.eclipse.uprotocol.uri.factory.UriFactory.ANY; +import static org.eclipse.uprotocol.uuid.serializer.UuidSerializer.serialize; import static org.junit.Assert.assertEquals; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.uuid.serializer.LongUuidSerializer; -import org.eclipse.uprotocol.v1.UAttributes; +import org.eclipse.uprotocol.uri.validator.UriFilter; import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UResource; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUID; import org.eclipse.uprotocol.v1.UUri; @@ -53,12 +51,7 @@ public class FormatterTest extends TestBase { private static final String KEY2 = "key2"; private static final String VALUE1 = "value1"; private static final String VALUE2 = "value2"; - private static final String MESSAGE1 = KEY1 + SEPARATOR_PAIR + VALUE1; - private static final String MESSAGE2 = MESSAGE1 + SEPARATOR_PAIRS + KEY2 + SEPARATOR_PAIR + VALUE2; private static final String METHOD = "method"; - private static final String MESSAGE_STATUS_OK = "status." + METHOD + SEPARATOR_PAIR + "[" + - Key.CODE + SEPARATOR_PAIR + getCode(STATUS_OK) + "]" + SEPARATOR_PAIRS + MESSAGE1; - private static final String ID_STRING = LongUuidSerializer.instance().serialize(ID); @Test public void testTag() { @@ -105,12 +98,12 @@ public void testGroupEmpty() { @Test public void testJoinGrouped() { - assertEquals("[" + MESSAGE1 + "]", Formatter.joinGrouped(KEY1, VALUE1)); + assertEquals("[key1: value1]", Formatter.joinGrouped(KEY1, VALUE1)); } @Test public void testJoin() { - assertEquals(MESSAGE2, Formatter.join(KEY1, VALUE1, KEY2, VALUE2)); + assertEquals("key1: value1, key2: value2", Formatter.join(KEY1, VALUE1, KEY2, VALUE2)); } @Test @@ -121,8 +114,8 @@ public void testJoinEmpty() { @Test public void testJoinEmptyKey() { - assertEquals(MESSAGE1, Formatter.join(KEY1, VALUE1, null, VALUE2)); - assertEquals(MESSAGE1, Formatter.join(KEY1, VALUE1, "", VALUE2)); + assertEquals("key1: value1", Formatter.join(KEY1, VALUE1, null, VALUE2)); + assertEquals("key1: value1", Formatter.join(KEY1, VALUE1, "", VALUE2)); } @Test @@ -135,7 +128,7 @@ public void testJoinAndAppend() { StringBuilder builder = new StringBuilder(); Formatter.joinAndAppend(builder, KEY1, VALUE1); Formatter.joinAndAppend(builder, KEY2, VALUE2); - assertEquals(MESSAGE2, builder.toString()); + assertEquals("key1: value1, key2: value2", builder.toString()); } @Test @@ -148,62 +141,44 @@ public void testJoinAndAppendAutoQuotes() { @Test public void testStatus() { - assertEquals(MESSAGE_STATUS_OK, Formatter.status(METHOD, STATUS_OK, KEY1, VALUE1)); + assertEquals("status.method: [code: OK], key1: value1", Formatter.status(METHOD, STATUS_OK, KEY1, VALUE1)); } @Test - public void testStringifyUUID() { - assertEquals(ID_STRING, Formatter.stringify(ID)); + public void testError() { + assertEquals("error: Message, reason: Reason, key1: value1", + Formatter.error("Message", new RuntimeException("Reason"), KEY1, VALUE1)); } @Test - public void testStringifyUUIDNull() { - assertEquals("", Formatter.stringify((UUID) null)); - } - - @Test - public void testStringifyUEntity() { - assertEquals("test.client/1", Formatter.stringify(CLIENT)); - } - - @Test - public void testStringifyUEntityWithoutVersionMajor() { - assertEquals("test.client", Formatter.stringify(UEntity.newBuilder(CLIENT).clearVersionMajor().build())); - } - - @Test - public void testStringifyUEntityNull() { - assertEquals("", Formatter.stringify((UEntity) null)); + public void testStringifyUUID() { + assertEquals(serialize(ID), Formatter.stringify(ID)); } @Test - public void testStringifyUResource() { - assertEquals("resource.main#State", Formatter.stringify(RESOURCE)); + public void testStringifyUUIDNull() { + assertEquals("", Formatter.stringify((UUID) null)); } @Test - public void testStringifyUResourceWithoutInstance() { - assertEquals("resource#State", Formatter.stringify(UResource.newBuilder(RESOURCE).clearInstance().build())); + public void testStringifyUUri() { + assertEquals("/51/1/8000", Formatter.stringify(RESOURCE_URI)); } @Test - public void testStringifyUResourceWithoutMessage() { - assertEquals("resource.main", Formatter.stringify(UResource.newBuilder(RESOURCE).clearMessage().build())); + public void testStringifyUUriNull() { + assertEquals("", Formatter.stringify((UUri) null)); } - @Test - public void testStringifyUResourceNull() { - assertEquals("", Formatter.stringify((UResource) null)); - } @Test - public void testStringifyUUri() { - assertEquals("/test.service/1/resource.main#State", Formatter.stringify(RESOURCE_URI)); + public void testStringifyUriFilter() { + assertEquals("[source: //*/ffff/ff/ffff, sink: /51/1/1]", Formatter.stringify(new UriFilter(ANY, METHOD_URI))); } @Test - public void testStringifyUUriNull() { - assertEquals("", Formatter.stringify((UUri) null)); + public void testStringifyUriFilterNull() { + assertEquals("", Formatter.stringify((UriFilter) null)); } @Test @@ -225,17 +200,16 @@ public void testStringifyUStatusNull() { @Test public void testStringifyUMessage() { - final UMessage message = buildMessage(PAYLOAD, ATTRIBUTES); - assertEquals("[id: " + ID_STRING + ", " + - "source: /test.service/1/rpc.method, sink: /test.client/1/rpc.response, " + - "type: UMESSAGE_TYPE_RESPONSE]", Formatter.stringify(message)); + final UMessage message = notification(RESOURCE_URI, CLIENT_URI).build(); + assertEquals("[id: " + serialize(message.getAttributes().getId()) + ", " + + "source: /51/1/8000, sink: /50/1/0, type: UMESSAGE_TYPE_NOTIFICATION]", Formatter.stringify(message)); } @Test public void testStringifyUMessageWithoutSink() { - final UMessage message = buildMessage(PAYLOAD, UAttributes.newBuilder(ATTRIBUTES).clearSink().build()); - assertEquals("[id: " + ID_STRING + ", " + - "source: /test.service/1/rpc.method, type: UMESSAGE_TYPE_RESPONSE]", Formatter.stringify(message)); + final UMessage message = publish(RESOURCE_URI).build(); + assertEquals("[id: " + serialize(message.getAttributes().getId()) + ", " + + "source: /51/1/8000, type: UMESSAGE_TYPE_PUBLISH]", Formatter.stringify(message)); } @Test @@ -250,4 +224,11 @@ public void testToPrettyMemory() { assertEquals("1.0 MB", Formatter.toPrettyMemory(1048576)); assertEquals("1.0 GB", Formatter.toPrettyMemory(1073741824)); } + + @Test + public void testToPrettyDuration() { + assertEquals("0h 0m 0s", Formatter.toPrettyDuration(0)); + assertEquals("2h 3m 4s", Formatter.toPrettyDuration(2 * 60 * 60 * 1000 + 3 * 60 * 1000 + 4 * 1000)); + assertEquals("0h 0m -1s", Formatter.toPrettyDuration( -1000)); + } } diff --git a/library/src/test/java/org/eclipse/uprotocol/core/ubus/UBusManagerTest.java b/library/src/test/java/org/eclipse/uprotocol/core/ubus/UBusManagerTest.java index f8a2eaa..3374d1b 100644 --- a/library/src/test/java/org/eclipse/uprotocol/core/ubus/UBusManagerTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/core/ubus/UBusManagerTest.java @@ -26,6 +26,7 @@ import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; import static org.eclipse.uprotocol.core.ubus.UBusManager.ACTION_BIND_UBUS; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.publish; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -55,9 +56,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.client.R; -import org.eclipse.uprotocol.common.UStatusException; +import org.eclipse.uprotocol.communication.UStatusException; +import org.eclipse.uprotocol.transport.R; import org.eclipse.uprotocol.transport.UListener; +import org.eclipse.uprotocol.uri.validator.UriFilter; import org.eclipse.uprotocol.v1.UCode; import org.eclipse.uprotocol.v1.UMessage; import org.eclipse.uprotocol.v1.UStatus; @@ -75,9 +77,9 @@ @RunWith(AndroidJUnit4.class) public class UBusManagerTest extends TestBase { - private static final String SERVICE_PACKAGE = "org.eclipse.uprotocol.core.ubus"; - private static final ComponentName SERVICE = new ComponentName(SERVICE_PACKAGE, SERVICE_PACKAGE + ".UBusService"); - private static final UMessage MESSAGE = buildMessage(PAYLOAD, buildPublishAttributes(RESOURCE_URI)); + private static final String SERVICE_PACKAGE = "org.eclipse.uprotocol.core"; + private static final ComponentName SERVICE = new ComponentName(SERVICE_PACKAGE, SERVICE_PACKAGE + ".UCoreService"); + private static final UMessage MESSAGE = publish(RESOURCE_URI).build(PAYLOAD); private static final long REBIND_DELAY_MS = 1000 + DELAY_MS; private Context mContext; @@ -94,7 +96,7 @@ public void setUp() throws RemoteException { mConnectionCallback = mock(ConnectionCallback.class); mListener = mock(UListener.class); setServiceConfig(SERVICE_PACKAGE); - mManager = new UBusManager(mContext, CLIENT, mConnectionCallback, mListener); + mManager = new UBusManager(mContext, CLIENT_URI, mConnectionCallback, mListener); mManager.setLoggable(Log.INFO); prepareService(); } @@ -110,11 +112,9 @@ private void prepareService() throws RemoteException { doReturn(mServiceBinder).when(mService).asBinder(); doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).registerClient(any(), any(), any(), anyInt(), any()); doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).unregisterClient(any()); - doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).enableDispatching(any(), anyInt(), any()); - doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).disableDispatching(any(), anyInt(), any()); + doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).enableDispatching(any(), any(), anyInt(), any()); + doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).disableDispatching(any(), any(), anyInt(), any()); doReturn(new ParcelableUStatus(STATUS_OK)).when(mService).send(any(), any()); - doReturn(new ParcelableUMessage[] { new ParcelableUMessage(MESSAGE) }) - .when(mService).pull(any(), anyInt(), anyInt(), any()); prepareService(true, connection -> { mServiceConnection = connection; mServiceConnection.onServiceConnected(SERVICE, mServiceBinder); @@ -178,7 +178,7 @@ public void testConnect() { public void testConnectWithConfiguredPackage() { final String packageName = "some.package"; setServiceConfig(packageName); - mManager = new UBusManager(mContext, CLIENT, mConnectionCallback, mListener); + mManager = new UBusManager(mContext, CLIENT_URI, mConnectionCallback, mListener); assertConnected(mManager.connect()); verify(mContext, times(1)).bindService(argThat(intent -> { assertEquals(ACTION_BIND_UBUS, intent.getAction()); @@ -190,7 +190,7 @@ public void testConnectWithConfiguredPackage() { @Test public void testConnectWithConfiguredComponent() { setServiceConfig(SERVICE.flattenToShortString()); - mManager = new UBusManager(mContext, CLIENT, mConnectionCallback, mListener); + mManager = new UBusManager(mContext, CLIENT_URI, mConnectionCallback, mListener); assertConnected(mManager.connect()); verify(mContext, times(1)).bindService(argThat(intent -> { assertEquals(ACTION_BIND_UBUS, intent.getAction()); @@ -202,7 +202,7 @@ public void testConnectWithConfiguredComponent() { @Test public void testConnectWithEmptyConfig() { setServiceConfig(""); - mManager = new UBusManager(mContext, CLIENT, mConnectionCallback, mListener); + mManager = new UBusManager(mContext, CLIENT_URI, mConnectionCallback, mListener); assertConnected(mManager.connect()); verify(mContext, times(1)).bindService(argThat(intent -> { assertEquals(ACTION_BIND_UBUS, intent.getAction()); @@ -414,72 +414,44 @@ public void testCalculateRebindDelaySeconds() { @Test public void testEnableDispatching() throws RemoteException { testConnect(); - assertStatus(UCode.OK, mManager.enableDispatching(RESOURCE_URI)); - verify(mService, times(1)).enableDispatching(eq(new ParcelableUUri(RESOURCE_URI)), anyInt(), any()); + assertStatus(UCode.OK, mManager.enableDispatching(new UriFilter(RESOURCE_URI, CLIENT_URI))); + verify(mService, times(1)).enableDispatching( + eq(new ParcelableUUri(RESOURCE_URI)), eq(new ParcelableUUri(CLIENT_URI)), anyInt(), any()); } @Test public void testEnableDispatchingDisconnected() throws RemoteException { - assertStatus(UCode.UNAVAILABLE, mManager.enableDispatching(RESOURCE_URI)); - verify(mService, never()).enableDispatching(any(), anyInt(), any()); + assertStatus(UCode.UNAVAILABLE, mManager.enableDispatching(new UriFilter(RESOURCE_URI, CLIENT_URI))); + verify(mService, never()).enableDispatching(any(), any(), anyInt(), any()); } @Test public void testDisableDispatching() throws RemoteException { testConnect(); - assertStatus(UCode.OK, mManager.disableDispatching(RESOURCE_URI)); - verify(mService, times(1)).disableDispatching(eq(new ParcelableUUri(RESOURCE_URI)), anyInt(), any()); + assertStatus(UCode.OK, mManager.disableDispatching(new UriFilter(RESOURCE_URI, CLIENT_URI))); + verify(mService, times(1)).disableDispatching( + eq(new ParcelableUUri(RESOURCE_URI)), eq(new ParcelableUUri(CLIENT_URI)), anyInt(), any()); } @Test public void testDisableDispatchingDisconnected() throws RemoteException { - assertStatus(UCode.UNAVAILABLE, mManager.disableDispatching(RESOURCE_URI)); - verify(mService, never()).disableDispatching(any(), anyInt(), any()); + assertStatus(UCode.UNAVAILABLE, mManager.disableDispatching(new UriFilter(RESOURCE_URI, CLIENT_URI))); + verify(mService, never()).disableDispatching(any(), any(), anyInt(), any()); } @Test public void testDisableDispatchingDisconnectedDebug() throws RemoteException { mManager.setLoggable(Log.DEBUG); - assertStatus(UCode.UNAVAILABLE, mManager.disableDispatching(RESOURCE_URI)); - verify(mService, never()).disableDispatching(any(), anyInt(), any()); + assertStatus(UCode.UNAVAILABLE, mManager.disableDispatching(new UriFilter(RESOURCE_URI, CLIENT_URI))); + verify(mService, never()).disableDispatching(any(), any(), anyInt(), any()); } @Test public void testDisableDispatchingQuietly() throws RemoteException { testConnect(); - mManager.disableDispatchingQuietly(RESOURCE_URI); - verify(mService, times(1)).disableDispatching(eq(new ParcelableUUri(RESOURCE_URI)), anyInt(), any()); - } - - @Test - public void testGetLastMessage() throws RemoteException { - testConnect(); - assertEquals(MESSAGE, mManager.getLastMessage(RESOURCE_URI)); - verify(mService, times(1)).pull(eq(new ParcelableUUri(RESOURCE_URI)), eq(1), anyInt(), any()); - } - - @Test - public void testGetLastMessageNotAvailable() throws RemoteException { - testConnect(); - doReturn(new ParcelableUMessage[0]).when(mService).pull(any(), anyInt(), anyInt(), any()); - assertNull(mManager.getLastMessage(RESOURCE_URI)); - doReturn(null).when(mService).pull(any(), anyInt(), anyInt(), any()); - assertNull(mManager.getLastMessage(RESOURCE_URI)); - verify(mService, times(2)).pull(eq(new ParcelableUUri(RESOURCE_URI)), eq(1), anyInt(), any()); - } - - @Test - @SuppressWarnings("DataFlowIssue") - public void testGetLastMessageInvalidArgument() throws RemoteException { - testConnect(); - doThrow(new NullPointerException()).when(mService).pull(any(), anyInt(), anyInt(), any()); - assertNull(mManager.getLastMessage(null)); - } - - @Test - public void testGetLastMessageDisconnected() throws RemoteException { - assertNull(mManager.getLastMessage(RESOURCE_URI)); - verify(mService, never()).pull(any(), anyInt(), anyInt(), any()); + mManager.disableDispatchingQuietly(new UriFilter(RESOURCE_URI, CLIENT_URI)); + verify(mService, times(1)).disableDispatching( + eq(new ParcelableUUri(RESOURCE_URI)), eq(new ParcelableUUri(CLIENT_URI)), anyInt(), any()); } @Test diff --git a/library/src/test/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscoveryTest.java b/library/src/test/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscoveryTest.java deleted file mode 100644 index 7f7a8cd..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/core/udiscovery/v3/UDiscoveryTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.udiscovery.v3; - -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.protobuf.DescriptorProtos.ServiceOptions; -import com.google.protobuf.Message; - -import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.UprotocolOptions; -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CompletableFuture; - -@RunWith(AndroidJUnit4.class) -public class UDiscoveryTest extends TestBase { - private RpcClient mClient; - private UDiscovery.Stub mStub; - - @Before - public void setUp() { - mClient = mock(RpcClient.class); - mStub = UDiscovery.newStub(mClient); - } - - private void simulateResponse(@NonNull Message response) { - doAnswer(invocation -> { - final UUri methodUri = invocation.getArgument(0); - final UAttributes responseAttributes = buildResponseAttributes(methodUri, RESPONSE_URI, ID); - return CompletableFuture.completedFuture(buildMessage(packToAny(response), responseAttributes)); - }).when(mClient).invokeMethod(any(), any(), any()); - } - - @Test - public void testEntity() { - final ServiceOptions options = UDiscoveryProto.getDescriptor().findServiceByName("uDiscovery").getOptions(); - assertEquals(options.getExtension(UprotocolOptions.name), UDiscovery.SERVICE.getName()); - assertEquals((int) options.getExtension(UprotocolOptions.versionMajor), UDiscovery.SERVICE.getVersionMajor()); - } - - @Test - public void testNewStub() { - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(DEFAULT_OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithCallOptions() { - mStub = UDiscovery.newStub(mClient, OPTIONS); - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithAuthorityAndCallOptions() { - mStub = UDiscovery.newStub(mClient, AUTHORITY_REMOTE, OPTIONS); - assertEquals(AUTHORITY_REMOTE, mStub.getAuthority().orElse(null)); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testLookupUri() { - final UUri request = UUri.getDefaultInstance(); - final LookupUriResponse response = LookupUriResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.lookupUri(request).toCompletableFuture())); - } - - @Test - public void testUpdateNode() { - final UpdateNodeRequest request = UpdateNodeRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.updateNode(request).toCompletableFuture())); - } - - @Test - public void testFindNodes() { - final FindNodesRequest request = FindNodesRequest.getDefaultInstance(); - final FindNodesResponse response = FindNodesResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.findNodes(request).toCompletableFuture())); - } - - @Test - public void testFindNodeProperties() { - final FindNodePropertiesRequest request = FindNodePropertiesRequest.getDefaultInstance(); - final FindNodePropertiesResponse response = FindNodePropertiesResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.findNodeProperties(request).toCompletableFuture())); - } - - @Test - public void testDeleteNodes() { - final DeleteNodesRequest request = DeleteNodesRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.deleteNodes(request).toCompletableFuture())); - } - - @Test - public void testAddNodes() { - final AddNodesRequest request = AddNodesRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.addNodes(request).toCompletableFuture())); - } - - @Test - public void testUnregisterForNotifications() { - final NotificationsRequest request = NotificationsRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.unregisterForNotifications(request).toCompletableFuture())); - } - - @Test - public void testResolveUri() { - final ResolveUriRequest request = ResolveUriRequest.getDefaultInstance(); - final ResolveUriResponse response = ResolveUriResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.resolveUri(request).toCompletableFuture())); - } - - @Test - public void testRegisterForNotifications() { - final NotificationsRequest request = NotificationsRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.registerForNotifications(request).toCompletableFuture())); - } - - @Test - public void testUpdateProperty() { - final UpdatePropertyRequest request = UpdatePropertyRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.updateProperty(request).toCompletableFuture())); - } - - @Test - public void tesCallWithAuthority() { - testNewStubWithAuthorityAndCallOptions(); - doReturn(new CompletableFuture<>()).when(mClient).invokeMethod(argThat(uri -> { - assertEquals(AUTHORITY_REMOTE, uri.getAuthority()); - assertEquals(UDiscovery.SERVICE, uri.getEntity()); - assertEquals(UResourceBuilder.forRpcRequest(UDiscovery.METHOD_LOOKUP_URI), uri.getResource()); - return true; - }), any(), any()); - assertFalse(mStub.lookupUri(UUri.getDefaultInstance()).toCompletableFuture().isDone()); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/core/usubscription/v3/USubscriptionTest.java b/library/src/test/java/org/eclipse/uprotocol/core/usubscription/v3/USubscriptionTest.java deleted file mode 100644 index ee7b71e..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/core/usubscription/v3/USubscriptionTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.usubscription.v3; - -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.protobuf.DescriptorProtos.ServiceOptions; -import com.google.protobuf.Message; - -import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.UprotocolOptions; -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CompletableFuture; - -@RunWith(AndroidJUnit4.class) -public class USubscriptionTest extends TestBase { - private RpcClient mClient; - private USubscription.Stub mStub; - - @Before - public void setUp() { - mClient = mock(RpcClient.class); - mStub = USubscription.newStub(mClient); - } - - private void simulateResponse(@NonNull Message response) { - doAnswer(invocation -> { - final UUri methodUri = invocation.getArgument(0); - final UAttributes responseAttributes = buildResponseAttributes(methodUri, RESPONSE_URI, ID); - return CompletableFuture.completedFuture(buildMessage(packToAny(response), responseAttributes)); - }).when(mClient).invokeMethod(any(), any(), any()); - } - - @Test - public void testEntity() { - final ServiceOptions options = USubscriptionProto.getDescriptor().findServiceByName("uSubscription").getOptions(); - assertEquals(options.getExtension(UprotocolOptions.name), USubscription.SERVICE.getName()); - assertEquals((int) options.getExtension(UprotocolOptions.versionMajor), USubscription.SERVICE.getVersionMajor()); - } - - @Test - public void testNewStub() { - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(DEFAULT_OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithCallOptions() { - mStub = USubscription.newStub(mClient, OPTIONS); - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithAuthorityAndCallOptions() { - mStub = USubscription.newStub(mClient, AUTHORITY_REMOTE, OPTIONS); - assertEquals(AUTHORITY_REMOTE, mStub.getAuthority().orElse(null)); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testSubscribe() { - final SubscriptionRequest request = SubscriptionRequest.getDefaultInstance(); - final SubscriptionResponse response = SubscriptionResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.subscribe(request).toCompletableFuture())); - } - - @Test - public void testUnsubscribe() { - final UnsubscribeRequest request = UnsubscribeRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.unsubscribe(request).toCompletableFuture())); - } - - @Test - public void testFetchSubscriptions() { - final FetchSubscriptionsRequest request = FetchSubscriptionsRequest.getDefaultInstance(); - final FetchSubscriptionsResponse response = FetchSubscriptionsResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.fetchSubscriptions(request).toCompletableFuture())); - } - - @Test - public void testCreateTopic() { - final CreateTopicRequest request = CreateTopicRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.createTopic(request).toCompletableFuture())); - } - - @Test - public void testDeprecateTopic() { - final DeprecateTopicRequest request = DeprecateTopicRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.deprecateTopic(request).toCompletableFuture())); - } - - @Test - public void testRegisterForNotifications() { - final NotificationsRequest request = NotificationsRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.registerForNotifications(request).toCompletableFuture())); - } - - @Test - public void testUnregisterForNotifications() { - final NotificationsRequest request = NotificationsRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.unregisterForNotifications(request).toCompletableFuture())); - } - - @Test - public void testFetchSubscribers() { - final FetchSubscribersRequest request = FetchSubscribersRequest.getDefaultInstance(); - final FetchSubscribersResponse response = FetchSubscribersResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.fetchSubscribers(request).toCompletableFuture())); - } - - @Test - public void testReset() { - final ResetRequest request = ResetRequest.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.reset(request).toCompletableFuture())); - } - - @Test - public void tesCallWithAuthority() { - testNewStubWithAuthorityAndCallOptions(); - doReturn(new CompletableFuture<>()).when(mClient).invokeMethod(argThat(uri -> { - assertEquals(AUTHORITY_REMOTE, uri.getAuthority()); - assertEquals(USubscription.SERVICE, uri.getEntity()); - assertEquals(UResourceBuilder.forRpcRequest(USubscription.METHOD_SUBSCRIBE), uri.getResource()); - return true; - }), any(), any()); - assertFalse(mStub.subscribe(SubscriptionRequest.getDefaultInstance()).toCompletableFuture().isDone()); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/core/utwin/v2/UTwinTest.java b/library/src/test/java/org/eclipse/uprotocol/core/utwin/v2/UTwinTest.java deleted file mode 100644 index 44011cc..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/core/utwin/v2/UTwinTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.core.utwin.v2; - -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.protobuf.DescriptorProtos.ServiceOptions; -import com.google.protobuf.Message; - -import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.UprotocolOptions; -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.v1.UUriBatch; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CompletableFuture; - -@RunWith(AndroidJUnit4.class) -public class UTwinTest extends TestBase { - private RpcClient mClient; - private UTwin.Stub mStub; - - @Before - public void setUp() { - mClient = mock(RpcClient.class); - mStub = UTwin.newStub(mClient); - } - - private void simulateResponse(@NonNull Message response) { - doAnswer(invocation -> { - final UUri methodUri = invocation.getArgument(0); - final UAttributes responseAttributes = buildResponseAttributes(methodUri, RESPONSE_URI, ID); - return CompletableFuture.completedFuture(buildMessage(packToAny(response), responseAttributes)); - }).when(mClient).invokeMethod(any(), any(), any()); - } - - @Test - public void testEntity() { - final ServiceOptions options = UTwinProto.getDescriptor().findServiceByName("uTwin").getOptions(); - assertEquals(options.getExtension(UprotocolOptions.name), UTwin.SERVICE.getName()); - assertEquals((int) options.getExtension(UprotocolOptions.versionMajor), UTwin.SERVICE.getVersionMajor()); - } - - @Test - public void testNewStub() { - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(DEFAULT_OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithCallOptions() { - mStub = UTwin.newStub(mClient, OPTIONS); - assertFalse(mStub.getAuthority().isPresent()); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testNewStubWithAuthorityAndCallOptions() { - mStub = UTwin.newStub(mClient, AUTHORITY_REMOTE, OPTIONS); - assertEquals(AUTHORITY_REMOTE, mStub.getAuthority().orElse(null)); - assertEquals(OPTIONS, mStub.getOptions()); - } - - @Test - public void testGetLastMessages() { - final UUriBatch request = UUriBatch.getDefaultInstance(); - final GetLastMessagesResponse response = GetLastMessagesResponse.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.getLastMessages(request).toCompletableFuture())); - } - - @Test - public void testSetLastMessage() { - final UMessage request = UMessage.getDefaultInstance(); - final UStatus response = UStatus.getDefaultInstance(); - simulateResponse(response); - assertEquals(response, getOrThrow(mStub.setLastMessage(request).toCompletableFuture())); - } - - @Test - public void tesCallWithAuthority() { - testNewStubWithAuthorityAndCallOptions(); - doReturn(new CompletableFuture<>()).when(mClient).invokeMethod(argThat(uri -> { - assertEquals(AUTHORITY_REMOTE, uri.getAuthority()); - assertEquals(UTwin.SERVICE, uri.getEntity()); - assertEquals(UResourceBuilder.forRpcRequest(UTwin.METHOD_SET_LAST_MESSAGE), uri.getResource()); - return true; - }), any(), any()); - assertFalse(mStub.setLastMessage(UMessage.getDefaultInstance()).toCompletableFuture().isDone()); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/transport/UTransportAndroidTest.java b/library/src/test/java/org/eclipse/uprotocol/transport/UTransportAndroidTest.java new file mode 100644 index 0000000..94e972d --- /dev/null +++ b/library/src/test/java/org/eclipse/uprotocol/transport/UTransportAndroidTest.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2024 General Motors GTO LLC + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * SPDX-FileType: SOURCE + * SPDX-FileCopyrightText: 2023 General Motors GTO LLC + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.transport; + +import static org.eclipse.uprotocol.common.util.UStatusUtils.STATUS_OK; +import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; +import static org.eclipse.uprotocol.transport.BuildConfig.LIBRARY_PACKAGE_NAME; +import static org.eclipse.uprotocol.transport.BuildConfig.VERSION_NAME; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.notification; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.publish; +import static org.eclipse.uprotocol.uri.factory.UriFactory.WILDCARD_ENTITY_ID; +import static org.eclipse.uprotocol.uri.factory.UriFactory.WILDCARD_ENTITY_VERSION; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Handler; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.eclipse.uprotocol.TestBase; +import org.eclipse.uprotocol.core.ubus.UBusManager; +import org.eclipse.uprotocol.uri.validator.UriFilter; +import org.eclipse.uprotocol.v1.UCode; +import org.eclipse.uprotocol.v1.UMessage; +import org.eclipse.uprotocol.v1.UStatus; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowLog; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@RunWith(AndroidJUnit4.class) +public class UTransportAndroidTest extends TestBase { + private static final UMessage MESSAGE = notification(RESOURCE_URI, CLIENT_URI).build(PAYLOAD); + private static final UMessage MESSAGE2 = publish(RESOURCE_URI).build(PAYLOAD); + private static final UriFilter FILTER = new UriFilter(RESOURCE_URI, CLIENT_URI); + private static final UriFilter FILTER2 = new UriFilter(RESOURCE2_URI, CLIENT_URI); + + private Context mContext; + private String mPackageName; + private ShadowPackageManager mShadowPackageManager; + private Handler mHandler; + private Executor mExecutor; + private UListener mListener; + private UListener mListener2; + private UBusManager mManager; + private UTransportAndroid mTransport; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.getApplication(); + mPackageName = mContext.getPackageName(); + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mHandler = newMockHandler(); + mExecutor = newMockExecutor(); + mListener = mock(UListener.class); + mListener2 = mock(UListener.class); + mManager = mock(UBusManager.class); + doCallRealMethod().when(mManager).disableDispatchingQuietly(any()); + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(CLIENT_URI))); + mTransport = new UTransportAndroid(mContext, CLIENT_URI, mManager, mExecutor); + mTransport.setLoggable(Log.INFO); + } + + private void injectPackage(@NonNull PackageInfo packageInfo) { + mShadowPackageManager.installPackage(packageInfo); + } + + @Test + public void testConstants() { + assertEquals("uprotocol.permission.ACCESS_UBUS", UTransportAndroid.PERMISSION_ACCESS_UBUS); + assertEquals("uprotocol.entity.id", UTransportAndroid.META_DATA_ENTITY_ID); + assertEquals("uprotocol.entity.version", UTransportAndroid.META_DATA_ENTITY_VERSION); + } + + @Test + public void testCreate() { + injectPackage(buildPackageInfo(mPackageName, + buildServiceInfo(new ComponentName(mPackageName, ".Service"), buildEntityMetadata(SERVICE_URI)))); + assertNotNull(UTransportAndroid.create(mContext, SERVICE_URI, mExecutor)); + } + + @Test + public void testCreateWithoutSource() { + assertNotNull(UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateWitHandler() { + assertNotNull(UTransportAndroid.create(mContext, mHandler)); + } + + @Test + public void testCreateWithDefaultCallbackThread() { + assertNotNull(UTransportAndroid.create(mContext, (Handler) null)); + assertNotNull(UTransportAndroid.create(mContext, (Executor) null)); + } + + @Test + public void testCreateWithBadContextWrapper() { + final ContextWrapper context = spy(new ContextWrapper(mContext)); + doReturn(null).when(context).getBaseContext(); + assertThrows(NullPointerException.class, () -> UTransportAndroid.create(context, mExecutor)); + } + + @Test + public void testCreatePackageManagerNotAvailable() { + assertThrows(NullPointerException.class, () -> UTransportAndroid.create(mock(Context.class), mExecutor)); + } + + @Test + public void testCreatePackageNotFound() throws NameNotFoundException { + final PackageManager manager = mock(PackageManager.class); + doThrow(new NameNotFoundException()).when(manager).getPackageInfo(anyString(), anyInt()); + final Context context = spy(new ContextWrapper(mContext)); + doReturn(manager).when(context).getPackageManager(); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(context, mExecutor)); + } + + @Test + public void testCreateEntityDifferentDeclared() { + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(SERVICE_ID, VERSION))); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, CLIENT_URI, mExecutor)); + } + + @Test + public void testCreateEntityNotDeclared() { + injectPackage(buildPackageInfo(mPackageName)); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateEntityIdNotDeclared() { + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(0, VERSION))); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateEntityIdInvalid() { + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(WILDCARD_ENTITY_ID, VERSION))); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateEntityVersionNotDeclared() { + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(CLIENT_ID, 0))); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateEntityVersionInvalid() { + injectPackage(buildPackageInfo(mPackageName, buildEntityMetadata(CLIENT_ID, WILDCARD_ENTITY_VERSION))); + assertThrows(SecurityException.class, () -> UTransportAndroid.create(mContext, mExecutor)); + } + + @Test + public void testCreateVerboseVersionLogged() { + final String tag = mTransport.getTag(); + ShadowLog.setLoggable(tag, Log.VERBOSE); + assertNotNull(UTransportAndroid.create(mContext, mTransport.getSource(), mHandler)); + ShadowLog.getLogsForTag(tag).stream() + .filter(it -> it.msg.contains(LIBRARY_PACKAGE_NAME) && it.msg.contains(VERSION_NAME)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Version is not printed")); + } + + @Test + public void testOpen() { + final CompletableFuture future = new CompletableFuture<>(); + doReturn(future).when(mManager).connect(); + assertEquals(future, mTransport.open()); + } + + @Test + public void testClose() { + doReturn(CompletableFuture.completedFuture(STATUS_OK)).when(mManager).disconnect(); + mTransport.close(); + verify(mManager, times(1)).disconnect(); + } + + @Test + public void testIsOpened() { + assertFalse(mTransport.isOpened()); + doReturn(true).when(mManager).isConnected(); + assertTrue(mTransport.isOpened()); + } + + @Test + public void testOnConnected() { + testOnConnectionInterrupted(); + mTransport.getConnectionCallback().onConnected(); + verify(mManager, times(2)).enableDispatching(any()); + } + + @Test + public void testOnDisconnected() { + testRegisterListener(); + mTransport.getConnectionCallback().onDisconnected(); + verify(mManager, never()).disableDispatching(any()); + mTransport.getConnectionCallback().onConnected(); + verify(mManager, times(1)).enableDispatching(any()); + } + + @Test + public void testOnConnectionInterrupted() { + testRegisterListener(); + mTransport.getConnectionCallback().onConnected(); + mTransport.getConnectionCallback().onConnectionInterrupted(); + verify(mManager, never()).disableDispatching(any()); + } + + @Test + public void testGetSource() { + assertEquals(CLIENT_URI, mTransport.getSource()); + } + + @Test + public void testSend() { + doReturn(STATUS_OK).when(mManager).send(MESSAGE); + assertStatus(UCode.OK, getOrThrow(mTransport.send(MESSAGE))); + } + + @Test + public void testRegisterListener() { + doReturn(STATUS_OK).when(mManager).enableDispatching(FILTER); + assertStatus(UCode.OK, getOrThrow(mTransport.registerListener(FILTER.source(), FILTER.sink(), mListener))); + verify(mManager, times(1)).enableDispatching(FILTER); + } + + @Test + public void testRegisterListenerWithNull() { + assertStatus(UCode.INVALID_ARGUMENT, getOrThrow(mTransport.registerListener(FILTER.source(), null))); + verify(mManager, never()).enableDispatching(any()); + } + + @Test + public void testRegisterListenerSame() { + testRegisterListener(); + assertStatus(UCode.OK, getOrThrow(mTransport.registerListener(FILTER.source(), FILTER.sink(), mListener))); + verify(mManager, times(1)).enableDispatching(FILTER); + } + + @Test + public void testRegisterListenerNotFirst() { + testRegisterListener(); + assertStatus(UCode.OK, getOrThrow(mTransport.registerListener(FILTER.source(), FILTER.sink(), mListener2))); + verify(mManager, times(1)).enableDispatching(FILTER); + } + + @Test + public void testRegisterListenerDifferentFilter() { + doReturn(STATUS_OK).when(mManager).enableDispatching(FILTER); + doReturn(STATUS_OK).when(mManager).enableDispatching(FILTER2); + assertStatus(UCode.OK, getOrThrow(mTransport.registerListener(FILTER.source(), FILTER.sink(), mListener))); + assertStatus(UCode.OK, getOrThrow(mTransport.registerListener(FILTER2.source(), FILTER2.sink(), mListener))); + verify(mManager, times(1)).enableDispatching(FILTER); + verify(mManager, times(1)).enableDispatching(FILTER2); + } + + @Test + public void testRegisterListenerFailed() { + doReturn(buildStatus(UCode.UNAUTHENTICATED)).when(mManager).enableDispatching(FILTER); + assertStatus(UCode.UNAUTHENTICATED, getOrThrow(mTransport.registerListener(FILTER.source(), FILTER.sink(), mListener))); + } + + @Test + public void testRegisterListenerWhenReconnected() { + testRegisterListener(); + mTransport.getConnectionCallback().onConnectionInterrupted(); + verify(mManager, timeout(DELAY_MS).times(0)).disableDispatching(FILTER); + mTransport.getConnectionCallback().onConnected(); + verify(mManager, timeout(DELAY_MS).times(2)).enableDispatching(FILTER); + } + + @Test + public void testUnregisterListener() { + testRegisterListener(); + doReturn(STATUS_OK).when(mManager).disableDispatching(FILTER); + assertStatus(UCode.OK, getOrThrow(mTransport.unregisterListener(FILTER.source(), FILTER.sink(), mListener))); + verify(mManager, times(1)).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerWithNullListener() { + assertStatus(UCode.INVALID_ARGUMENT, getOrThrow(mTransport.unregisterListener(FILTER.source(), null))); + verify(mManager, never()).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerSame() { + testUnregisterListener(); + assertStatus(UCode.OK, getOrThrow(mTransport.unregisterListener(FILTER.source(), FILTER.sink(), mListener))); + verify(mManager, times(1)).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerNotRegistered() { + testRegisterListener(); + assertStatus(UCode.OK, getOrThrow(mTransport.unregisterListener(FILTER.source(), FILTER.sink(), mListener2))); + verify(mManager, times(0)).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerNotLast() { + testRegisterListenerNotFirst(); + assertStatus(UCode.OK, getOrThrow(mTransport.unregisterListener(FILTER.source(), FILTER.sink(), mListener))); + verify(mManager, never()).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerLast() { + testUnregisterListenerNotLast(); + assertStatus(UCode.OK, getOrThrow(mTransport.unregisterListener(FILTER.source(), FILTER.sink(), mListener2))); + verify(mManager, times(1)).disableDispatching(FILTER); + } + + @Test + public void testUnregisterListenerWhenDisconnected() { + testRegisterListener(); + mTransport.getConnectionCallback().onDisconnected(); + mTransport.getListener().onReceive(MESSAGE); + verify(mListener, timeout(DELAY_MS).times(0)).onReceive(MESSAGE); + } + + @Test + public void testOnReceiveMessage() { + testRegisterListenerNotFirst(); + mTransport.getListener().onReceive(MESSAGE); + verify(mListener, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); + verify(mListener2, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); + } + + @Test + public void testOnReceiveMessageNotRegistered() { + testUnregisterListener(); + mTransport.getListener().onReceive(MESSAGE); + verify(mListener, timeout(DELAY_MS).times(0)).onReceive(MESSAGE); + } + + @Test + public void testOnReceiveMessageListenerFailure() { + mTransport.setLoggable(Log.VERBOSE); + doThrow(new RuntimeException()).when(mListener).onReceive(any()); + testRegisterListenerNotFirst(); + mTransport.getListener().onReceive(MESSAGE); + verify(mListener, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); + verify(mListener2, timeout(DELAY_MS).times(1)).onReceive(MESSAGE); + } + + @Test + public void testOnReceiveMessageNotMatching() { + testRegisterListener(); + mTransport.getListener().onReceive(MESSAGE2); + verify(mListener, timeout(DELAY_MS).times(0)).onReceive(MESSAGE2); + } +} diff --git a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntityTest.java b/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntityTest.java deleted file mode 100644 index 607de45..0000000 --- a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUEntityTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2024 General Motors GTO LLC - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * SPDX-FileType: SOURCE - * SPDX-FileCopyrightText: 2023 General Motors GTO LLC - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.v1.internal; - -import static org.junit.Assert.assertEquals; - -import android.os.Parcel; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.eclipse.uprotocol.TestBase; -import org.eclipse.uprotocol.v1.UEntity; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class ParcelableUEntityTest extends TestBase { - private Parcel mParcel; - - @Before - public void setUp() { - mParcel = Parcel.obtain(); - } - - @After - public void tearDown() { - mParcel.recycle(); - } - - private void checkWriteAndRead(@NonNull UEntity message) { - final int start = mParcel.dataPosition(); - new ParcelableUEntity(message).writeToParcel(mParcel, 0); - mParcel.setDataPosition(start); - assertEquals(message, ParcelableUEntity.CREATOR.createFromParcel(mParcel).getWrapped()); - } - - @Test - public void testConstructor() { - assertEquals(SERVICE, new ParcelableUEntity(SERVICE).getWrapped()); - } - - @Test - public void testNewArray() { - final ParcelableUEntity[] array = ParcelableUEntity.CREATOR.newArray(2); - assertEquals(2, array.length); - } - - @Test - public void testCreateFromParcel() { - checkWriteAndRead(SERVICE); - } - - @Test - public void testCreateFromParcelWithoutVersionMajor() { - checkWriteAndRead(UEntity.newBuilder(SERVICE).clearVersionMajor().build()); - } - - @Test - public void testCreateFromParcelWithVersionMinor() { - checkWriteAndRead(UEntity.newBuilder(SERVICE).setVersionMinor(0).build()); - } - - @Test - public void testCreateFromParcelWithId() { - checkWriteAndRead(UEntity.newBuilder(SERVICE).setId(10).build()); - } - - @Test - public void testCreateFromParcelEmpty() { - checkWriteAndRead(UEntity.getDefaultInstance()); - } -} diff --git a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUMessageTest.java b/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUMessageTest.java index 9421776..d02f08f 100644 --- a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUMessageTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUMessageTest.java @@ -23,6 +23,7 @@ */ package org.eclipse.uprotocol.v1.internal; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.request; import static org.junit.Assert.assertEquals; import android.os.Parcel; @@ -39,7 +40,7 @@ @RunWith(AndroidJUnit4.class) public class ParcelableUMessageTest extends TestBase { - private static final UMessage MESSAGE = buildMessage(PAYLOAD, ATTRIBUTES); + private static final UMessage MESSAGE = request(CLIENT_URI, METHOD_URI, TTL).build(PAYLOAD); private Parcel mParcel; diff --git a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUUriTest.java b/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUUriTest.java index aa1c2db..3a8ae6b 100644 --- a/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUUriTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/v1/internal/ParcelableUUriTest.java @@ -76,17 +76,17 @@ public void testCreateFromParcel() { @Test public void testCreateFromParcelWithoutAuthority() { - checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearAuthority().build()); + checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearAuthorityName().build()); } @Test public void testCreateFromParcelWithoutEntity() { - checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearEntity().build()); + checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearUeId().clearUeVersionMajor().build()); } @Test public void testCreateFromParcelWithoutResource() { - checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearResource().build()); + checkWriteAndRead(UUri.newBuilder(RESOURCE_URI_REMOTE).clearResourceId().build()); } @Test diff --git a/library/src/test/java/org/eclipse/uprotocol/v1/internal/PerformanceTest.java b/library/src/test/java/org/eclipse/uprotocol/v1/internal/PerformanceTest.java index ae0e34b..2b2e9c6 100644 --- a/library/src/test/java/org/eclipse/uprotocol/v1/internal/PerformanceTest.java +++ b/library/src/test/java/org/eclipse/uprotocol/v1/internal/PerformanceTest.java @@ -24,7 +24,7 @@ package org.eclipse.uprotocol.v1.internal; import static org.eclipse.uprotocol.common.util.UStatusUtils.buildStatus; -import static org.eclipse.uprotocol.transport.builder.UPayloadBuilder.packToAny; +import static org.eclipse.uprotocol.transport.builder.UMessageBuilder.request; import android.os.Parcel; import android.os.Parcelable; @@ -34,7 +34,6 @@ import org.eclipse.uprotocol.TestBase; import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UEntity; import org.eclipse.uprotocol.v1.UMessage; import org.eclipse.uprotocol.v1.UStatus; import org.eclipse.uprotocol.v1.UUri; @@ -46,10 +45,9 @@ @RunWith(AndroidJUnit4.class) @SuppressWarnings("java:S2699") public class PerformanceTest extends TestBase { - private static final UEntity ENTITY = CLIENT; private static final UUri URI = RESOURCE_URI; private static final UStatus STATUS = buildStatus(UCode.UNKNOWN, "Unknown error"); - private static final UMessage MESSAGE = buildMessage(packToAny(STATUS), ATTRIBUTES); + private static final UMessage MESSAGE = request(CLIENT_URI, METHOD_URI, TTL).withToken(TOKEN).build(PAYLOAD); private static final String PROTOBUF = "Protobuf"; private static final List COUNTS = List.of(1000, 100, 10, 5, 1); @@ -73,15 +71,6 @@ private float readParcelable(@NonNull Parcel parcel, @NonNull Parcelable.Creator return (float) (end - start) / (float) count; } - private void runPerformanceTestParcelableUEntity(int count) { - final ParcelableUEntity parcelable = new ParcelableUEntity(ENTITY); - final Parcel parcel = Parcel.obtain(); - float writeAverage = writeParcelable(parcel, parcelable, count); - float readAverage = readParcelable(parcel, ParcelableUEntity.CREATOR, count); - parcel.recycle(); - printTableRow(count, writeAverage, readAverage, PROTOBUF); - } - private void runPerformanceTestParcelableUUri(int count) { final ParcelableUUri parcelable = new ParcelableUUri(URI); final Parcel parcel = Parcel.obtain(); @@ -124,12 +113,6 @@ private void runPerformanceTestUMessage(int count) { runPerformanceTestParcelableUMessage(count); } - @Test - public void testPerformanceUEntity() { - printTableHeader("UEntity"); - COUNTS.forEach(this::runPerformanceTestParcelableUEntity); - } - @Test public void testPerformanceUUri() { printTableHeader("UUri"); diff --git a/settings.gradle b/settings.gradle index 401576c..4cfeb6b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,5 +26,5 @@ dependencyResolutionManagement { } } -rootProject.name = 'uProtocol Client Android Library' +rootProject.name = 'uProtocol Transport Android Library' include ':library'