Skip to content

Commit 11f0b31

Browse files
authored
Feature/add dke ping for health messages (#221)
* Remove Ar2QA and update URLs in QA Deleted the `Ar2QA` class to eliminate redundancy and adjusted the URL constants in `QA` class to point to the correct QA environment. This streamlines the QA environment handling within the codebase. * Add support for MQTT communication unit and DKE ping Introduced MQTT_COMMUNICATION_UNIT in OnboardingResponseRepository and added a corresponding fixture. Updated certification details and added DKE_PING to SystemMessageType for improved system messaging capabilities. * Add Ping Service implementation Introduced PingService interface and its implementation, PingServiceImpl, to handle the pinging of health interfaces using MQTT protocol. Created PingParameters class for encapsulating ping request parameters and updated MessageEncoder to support encoding of ping messages. Also added a PingServiceTest class to validate the sending of health messages. * Correct formatting in AbstractIntegrationTest Fixed the indentation of the end-private-key line in AbstractIntegrationTest. Also removed unused import statements in PingServiceTest to clean up the code. * Enable test and update registration code and onboarding response Re-enabled the `onboardCommunicationUnitAndSaveToFile` test and updated its registration code. Also updated the `communication-unit.json` with new device and authentication details. * Update farming software JSON and adjust onboarding test Updated the `farming-software.json` with new device identifiers, connection criteria, and authentication details. Also, modified the test fixture to enable the onboarding test and updated the registration code accordingly. * Add new onboarding response JSON and update test classes Added a new `mqtt-communication-unit.json` file for onboarding responses. Corrected the file name reference in `OnboardingResponseRepository` and updated the registration code in the `MqttCommunicationUnitFixture` test case, while also re-enabling the previously disabled test. * Update telemetry platform onboarding response and test fixture Replaced telemetry platform onboarding details with new identifiers and updated the registration code in the test fixture. Also, enabled the previously disabled test for onboarding. * Remove obsolete onboarding responses JSON files Deleted JSON files for deactivated and removed farming software as well as telemetry platform to clean up outdated resources. These files are no longer needed and have been removed to maintain a leaner project repository. * Remove obsolete onboarding responses JSON files Deleted JSON files for deactivated and removed farming software as well as telemetry platform to clean up outdated resources. These files are no longer needed and have been removed to maintain a leaner project repository. * Refactor MQTT test cases for better integration Updated `PingServiceTest` to extend `AbstractIntegrationTest` and added necessary imports and services for MQTT testing. Modified `MqttCommunicationUnitFixture` to use MQTT instead of REST and added a logger for message delivery confirmation. Adjusted onboarding response JSON to match the new configuration. * Enhance MQTT Ping Service tests and fix onboarding fixture Refactored `PingServiceTest` to include message delivery and arrival checks. Updated `MqttCommunicationUnitFixture` to match the new test structure and modified the registration code in onboarding parameters. This ensures robust message handling and consistency in tests. * Refactor `PingServiceTest` and enhance message handling. Refactored the `PingServiceTest` to use instance variables instead of static. Enhance the `messageArrived` method to decode and validate incoming messages, improving test reliability and logging. * Remove unused imports and exception handling Removed unused import statements from three test classes to clean up the code. Additionally, eliminated the unnecessary throws declaration in `messageArrived` method in `PingServiceTest.java`. * Update project version to 3.3.0 Bump the parent project and all sub-modules from version 3.2.1 to 3.3.0. This ensures all modules are aligned with the latest version for consistency. * Refactor: Change async ping method to throw exception The `sendAsync` method in `PingServiceImpl` now throws a `RuntimeException`, instructing users to use the synchronous method instead. This change ensures clarity on the method's availability and usage.
1 parent 397b27c commit 11f0b31

25 files changed

Lines changed: 447 additions & 341 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ buildNumber.properties
2525
.settings
2626

2727
# GENERATED #
28-
src/main/generated
28+
src/main/generated
29+
30+
# LCK #
31+
*.lck

agrirouter-sdk-java-api/src/main/java/com/dke/data/agrirouter/api/env/Ar2QA.java

Lines changed: 0 additions & 27 deletions
This file was deleted.

agrirouter-sdk-java-api/src/main/java/com/dke/data/agrirouter/api/env/QA.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22

33
/**
44
* Abstraction of the QA environment, currently no overrides because the default is QA already.
5-
*
6-
* @deprecated This class is deprecated and will be removed in future versions.
7-
* The new agrirouter environment will be the default to use
85
*/
9-
@Deprecated(since = "3.2.0", forRemoval = true)
106
public abstract class QA implements Environment {
117

12-
private static final String ENV_BASE_URL = "https://agrirouter-qa.cfapps.eu10.hana.ondemand.com";
8+
private static final String ENV_BASE_URL = "https://app.qa.agrirouter.farm";
139
private static final String API_PREFIX = "/api/v1.0";
1410
private static final String REGISTRATION_SERVICE_URL =
15-
"https://agrirouter-registration-service-hubqa-eu10.cfapps.eu10.hana.ondemand.com";
11+
"https://endpoint-service.qa.agrirouter.farm";
1612

1713
@Override
1814
public String getEnvironmentBaseUrl() {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.dke.data.agrirouter.api.service.messaging.mqtt;
2+
3+
import com.dke.data.agrirouter.api.service.parameters.PingParameters;
4+
5+
/**
6+
* Service interface for pinging the health interface.
7+
*/
8+
public interface PingService extends MessagingService<PingParameters> {
9+
}

agrirouter-sdk-java-api/src/main/kotlin/com/dke/data/agrirouter/api/enums/SystemMessageType.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ enum class SystemMessageType(private val key: String, private val typeUrl: Strin
2929
DKE_FEED_CONFIRM("dke:feed_confirm", FeedRequests.MessageConfirm.getDescriptor().fullName),
3030
DKE_FEED_DELETE("dke:feed_delete", FeedRequests.MessageDelete.getDescriptor().fullName),
3131
DKE_FEED_MESSAGE_QUERY("dke:feed_message_query", FeedRequests.MessageQuery.getDescriptor().fullName),
32-
DKE_FEED_HEADER_QUERY("dke:feed_header_query", FeedRequests.MessageQuery.getDescriptor().fullName);
32+
DKE_FEED_HEADER_QUERY("dke:feed_header_query", FeedRequests.MessageQuery.getDescriptor().fullName),
33+
DKE_PING("dke:ping", "");
3334

3435
override fun getKey(): String {
3536
return key
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.dke.data.agrirouter.api.service.parameters
2+
3+
import com.dke.data.agrirouter.api.dto.onboard.OnboardingResponse
4+
import com.dke.data.agrirouter.api.service.ParameterValidation
5+
import com.dke.data.agrirouter.api.service.parameters.base.AbstractParameterBase
6+
7+
/**
8+
* Parameters class. Encapsulation for the services.
9+
*/
10+
class PingParameters : AbstractParameterBase(), ParameterValidation {
11+
12+
var onboardingResponse: OnboardingResponse? = null
13+
14+
override fun technicalValidation() {
15+
nullCheck("onboardingResponse", onboardingResponse)
16+
}
17+
18+
}

agrirouter-sdk-java-impl/src/main/java/com/dke/data/agrirouter/impl/messaging/MessageEncoder.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.dke.data.agrirouter.api.service.parameters.*;
1717
import com.dke.data.agrirouter.api.util.TimestampUtil;
1818
import com.dke.data.agrirouter.impl.common.MessageIdService;
19+
import com.google.protobuf.ByteString;
1920

2021
import java.util.Objects;
2122

@@ -450,10 +451,45 @@ default EncodedMessage encode(CloudOffboardingParameters parameters) {
450451
return new EncodedMessage(applicationMessageID, encodedMessage);
451452
}
452453

454+
/**
455+
* Encode a message to send a message.
456+
*
457+
* @param parameters -
458+
* @return -
459+
*/
460+
default EncodedMessage encode(PingParameters parameters) {
461+
final var applicationMessageID =
462+
parameters.getApplicationMessageId() == null
463+
? MessageIdService.generateMessageId()
464+
: parameters.getApplicationMessageId();
465+
466+
var messageContent = ""; // No content for ping messages.
467+
468+
var messageHeaderParameters = new MessageHeaderParameters();
469+
messageHeaderParameters.setApplicationMessageId(applicationMessageID);
470+
messageHeaderParameters.setTechnicalMessageType(SystemMessageType.DKE_PING);
471+
messageHeaderParameters.setMode(Request.RequestEnvelope.Mode.DIRECT);
472+
messageHeaderParameters.setMetadata(MessageOuterClass.Metadata.newBuilder().build());
473+
474+
setSequenceNumber(
475+
messageHeaderParameters,
476+
parameters.getSequenceNumber(),
477+
parameters.getOnboardingResponse());
478+
var payloadParameters = new PayloadParameters();
479+
payloadParameters.setTypeUrl(SystemMessageType.DKE_PING.getTypeUrl());
480+
481+
payloadParameters.setValue(ByteString.copyFrom(messageContent.getBytes()));
482+
483+
var encodedMessage =
484+
this.getEncodeMessageService().encode(messageHeaderParameters, payloadParameters);
485+
return new EncodedMessage(applicationMessageID, encodedMessage);
486+
}
487+
453488
/**
454489
* Get the service to encode messages.
455490
*
456491
* @return -
457492
*/
458493
EncodeMessageService getEncodeMessageService();
494+
459495
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.dke.data.agrirouter.impl.messaging.mqtt;
2+
3+
import com.dke.data.agrirouter.api.exception.CouldNotSendMqttMessageException;
4+
import com.dke.data.agrirouter.api.messaging.MqttAsyncMessageSendingResult;
5+
import com.dke.data.agrirouter.api.service.messaging.encoding.EncodeMessageService;
6+
import com.dke.data.agrirouter.api.service.messaging.mqtt.PingService;
7+
import com.dke.data.agrirouter.api.service.parameters.PingParameters;
8+
import com.dke.data.agrirouter.api.service.parameters.SendMessageParameters;
9+
import com.dke.data.agrirouter.impl.messaging.MessageBodyCreator;
10+
import com.dke.data.agrirouter.impl.messaging.MessageEncoder;
11+
import com.dke.data.agrirouter.impl.messaging.MqttService;
12+
import com.dke.data.agrirouter.impl.messaging.encoding.EncodeMessageServiceImpl;
13+
import org.eclipse.paho.client.mqttv3.IMqttClient;
14+
import org.eclipse.paho.client.mqttv3.MqttException;
15+
import org.eclipse.paho.client.mqttv3.MqttMessage;
16+
17+
import java.util.Collections;
18+
import java.util.Objects;
19+
20+
/**
21+
* Service implementation.
22+
*/
23+
public class PingServiceImpl extends MqttService
24+
implements PingService, MessageBodyCreator, MessageEncoder {
25+
26+
private final EncodeMessageService encodeMessageService;
27+
28+
public PingServiceImpl(IMqttClient mqttClient) {
29+
super(mqttClient);
30+
this.encodeMessageService = new EncodeMessageServiceImpl();
31+
}
32+
33+
@Override
34+
public String send(PingParameters parameters) {
35+
try {
36+
var encodedMessage = this.encode(parameters);
37+
var sendMessageParameters = new SendMessageParameters();
38+
sendMessageParameters.setOnboardingResponse(parameters.getOnboardingResponse());
39+
sendMessageParameters.setEncodedMessages(
40+
Collections.singletonList(encodedMessage.getEncodedMessage()));
41+
var messageAsJson = this.createMessageBody(sendMessageParameters);
42+
var payload = messageAsJson.getBytes();
43+
this.getMqttClient()
44+
.publish(
45+
Objects.requireNonNull(parameters.getOnboardingResponse())
46+
.getConnectionCriteria()
47+
.getMeasures(),
48+
new MqttMessage(payload));
49+
return encodedMessage.getApplicationMessageID();
50+
} catch (MqttException e) {
51+
throw new CouldNotSendMqttMessageException(e);
52+
}
53+
}
54+
55+
@Override
56+
public MqttAsyncMessageSendingResult sendAsync(PingParameters parameters) {
57+
throw new RuntimeException("Not implemented, please use the synchronous send method.");
58+
}
59+
60+
@Override
61+
public EncodeMessageService getEncodeMessageService() {
62+
return encodeMessageService;
63+
}
64+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"deviceAlternateId":"cdee8e0c-05b8-4252-9da3-977239e54adc","capabilityAlternateId":"3035ec70-dca1-4d71-a000-e79eb5891f81","sensorAlternateId":"f658fa1a-fef3-43bc-87e7-4499858ac609","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/cdee8e0c-05b8-4252-9da3-977239e54adc","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/cdee8e0c-05b8-4252-9da3-977239e54adc"},"authentication":{"type":"P12","secret":"FNG3X?X4gJwZlut!ebZ3rigVfQCyOrOqhCj7","certificate":"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"}}
1+
{"deviceAlternateId":"5dade5c9-dcca-4bf1-be22-f7f61ed90ea7","capabilityAlternateId":"5dade5c9-dcca-4bf1-be22-f7f61ed90ea7","sensorAlternateId":"5dade5c9-dcca-4bf1-be22-f7f61ed90ea7","connectionCriteria":{"gatewayId":"3","measures":"https://gateway-service.qa.agrirouter.farm/inbox/5dade5c9-dcca-4bf1-be22-f7f61ed90ea7","commands":"https://gateway-service.qa.agrirouter.farm/outbox/5dade5c9-dcca-4bf1-be22-f7f61ed90ea7"},"authentication":{"type":"P12","secret":"Ga4OM1C4tgWGILrF","certificate":"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\u003d"}}

0 commit comments

Comments
 (0)