diff --git a/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSAddressBookResource.java b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSAddressBookResource.java index 6ed4a0d..a119a08 100644 --- a/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSAddressBookResource.java +++ b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSAddressBookResource.java @@ -19,17 +19,22 @@ */ package org.openremote.extension.ems.manager.gopacs; -import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.Path; -import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Response; -import static jakarta.ws.rs.core.MediaType.APPLICATION_XML; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -@Path("v2/participants/DSO") +@Path("uftp-participants/v3/participants") public interface GOPACSAddressBookResource { @GET - @Consumes(APPLICATION_XML) - Response fetchParticipants(@QueryParam("contractedEan") String contractedEan); + @Path("{uftpDomainName}") + @Produces(APPLICATION_JSON) + Response fetchParticipantByDomain( + @HeaderParam("Authorization") String authorization, + @PathParam("uftpDomainName") String uftpDomainName + ); } diff --git a/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSHandler.java b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSHandler.java index 5443d56..0871d66 100644 --- a/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSHandler.java +++ b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/GOPACSHandler.java @@ -25,7 +25,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.Response; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.lfenergy.shapeshifter.api.*; @@ -88,8 +87,10 @@ public class GOPACSHandler implements UftpPayloadHandler, UftpParticipantService private static final Logger LOG = SyslogCategory.getLogger(API, GOPACSHandler.class); public static final String GOPACS_PRIVATE_KEY_FILE = "GOPACS_PRIVATE_KEY_FILE"; + public static final String GOPACS_BROKER_URL = "GOPACS_BROKER_URL"; + public static final String DEFAULT_GOPACS_BROKER_URL = "https://clc-message-broker.gopacs-services.eu"; public static final String GOPACS_PARTICIPANT_URL = "GOPACS_PARTICIPANT_URL"; - public static final String DEFAULT_GOPACS_PARTICIPANT_URL = "https://clc-message-broker.gopacs-services.eu"; + public static final String DEFAULT_GOPACS_PARTICIPANT_URL = "https://api.gopacs-services.eu"; public static final String GOPACS_OAUTH2_URL = "GOPACS_OAUTH2_URL"; public static final String DEFAULT_GOPACS_OAUTH2_URL = "https://auth.gopacs-services.eu/realms/gopacs/protocol/openid-connect/token"; public static final String GOPACS_CLIENT_ID = "GOPACS_CLIENT_ID"; @@ -107,6 +108,7 @@ public class GOPACSHandler implements UftpPayloadHandler, UftpParticipantService protected final String contractedEAN; protected final String electricitySupplierAssetId; protected final String realm; + protected final String gopacsBrokerUrl; protected final Map participants; protected final AssetProcessingService assetProcessingService; @@ -160,6 +162,8 @@ protected GOPACSHandler(String contractedEAN, String realm, String electricitySu this.timerService = container.getService(TimerService.class); this.webService = container.getService(WebService.class); + // Strip trailing slashes so the synthesised broker endpoint never contains a double slash + this.gopacsBrokerUrl = container.getConfig().getOrDefault(GOPACS_BROKER_URL, DEFAULT_GOPACS_BROKER_URL).replaceAll("/+$", ""); this.responseDelaySeconds = Integer.parseInt(container.getConfig().getOrDefault(GOPACS_RESPONSE_DELAY_SECONDS, DEFAULT_GOPACS_RESPONSE_DELAY_SECONDS)); this.flexOfferDelaySeconds = Integer.parseInt(container.getConfig().getOrDefault(GOPACS_FLEX_OFFER_DELAY_SECONDS, DEFAULT_GOPACS_FLEX_OFFER_DELAY_SECONDS)); @@ -293,8 +297,15 @@ public void notifyNewOutgoingMessage(OutgoingUftpMessage getParticipantInformation(USEFRoleType role, String domain) { if (participants.containsKey(domain)) { return Optional.of(participants.get(domain)); - } else { - try (Response response = gopacsAddressBookResource.fetchParticipants(contractedEAN)) { - if (response != null && response.getStatus() == 200) { - List participants = response.readEntity(new GenericType<>() { - }); - for (UftpParticipantInformation participant : participants) { - this.participants.put(participant.domain(), new UftpParticipantInformation(participant.domain(), participant.publicKey(), participant.endpoint(), true)); - } - return participants.stream().filter(p -> p.domain().equals(domain)).findFirst(); - } - } catch (Exception e) { - if (e.getCause() != null && e.getCause() instanceof IOException) { - LOG.log(Level.SEVERE, "Exception when requesting participant information", e.getCause()); - } else { - LOG.log(Level.SEVERE, "Exception when requesting participant information", e); - } + } + + String authorization = fetchBearerToken(); + if (authorization.isBlank()) { + LOG.warning("Skipping participant lookup for " + domain + ": no OAuth2 bearer token available"); + return Optional.empty(); + } + try (Response response = gopacsAddressBookResource.fetchParticipantByDomain(authorization, domain)) { + int status = response != null ? response.getStatus() : -1; + if (status == 200) { + ParticipantView view = response.readEntity(ParticipantView.class); + UftpParticipantInformation info = new UftpParticipantInformation(view.domain(), view.publicKey(), this.gopacsBrokerUrl + "/shapeshifter/api/v3/message", true); + participants.put(view.domain(), info); + return Optional.of(info); } + if (status == 404) { + LOG.fine("Participant not found in GOPACS address book: " + domain); + } else { + LOG.severe("Unexpected status " + status + " when requesting participant information for " + domain); + } + } catch (Exception e) { + Throwable cause = e.getCause() instanceof IOException ? e.getCause() : e; + LOG.log(Level.SEVERE, "Exception when requesting participant information for " + domain, cause); } return Optional.empty(); } diff --git a/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/ParticipantView.java b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/ParticipantView.java new file mode 100644 index 0000000..f956a3a --- /dev/null +++ b/ems/src/main/java/org/openremote/extension/ems/manager/gopacs/ParticipantView.java @@ -0,0 +1,25 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * See the CONTRIBUTORS.txt file in the distribution for a + * full listing of individual contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.openremote.extension.ems.manager.gopacs; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record ParticipantView(String domain, String publicKey) {} diff --git a/gradle.properties b/gradle.properties index db4133a..80a6b61 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,5 +18,5 @@ openremoteVersion = 1.22.1 bouncyCastleVersion = 1.81 jacksonVersion = 2.21.2 resteasyVersion = 6.2.15.Final -shapeshifterVersion = 3.2.2 +shapeshifterVersion = 3.5.0 testLoggerVersion = 4.0.0