Allow cross-binding bridges (#5083)

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer
2025-11-23 00:22:09 +01:00
committed by GitHub
parent 75b885a137
commit fc9c923621
7 changed files with 34 additions and 21 deletions
@@ -115,7 +115,6 @@ public class DiscoveryResultBuilder {
* @return the updated builder
*/
public DiscoveryResultBuilder withBridge(@Nullable ThingUID bridgeUID) {
validateThingUID(bridgeUID);
this.bridgeUID = bridgeUID;
return this;
}
@@ -164,14 +163,6 @@ public class DiscoveryResultBuilder {
ttl);
}
private void validateThingUID(@Nullable ThingUID bridgeUID) {
if (bridgeUID != null && (!thingUID.getBindingId().equals(bridgeUID.getBindingId())
|| !thingUID.getBridgeIds().contains(bridgeUID.getId()))) {
throw new IllegalArgumentException(
"Thing UID '" + thingUID + "' does not match bridge UID '" + bridgeUID + "'");
}
}
private String getStackTrace(final Thread thread) {
StackTraceElement[] elements = thread.getStackTrace();
return Arrays.stream(elements).map(element -> "\tat " + element.toString()).collect(Collectors.joining("\n"));
@@ -15,7 +15,6 @@ package org.openhab.core.config.discovery;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Map;
@@ -107,8 +106,10 @@ public class DiscoveryResultBuilderTest {
@Test
public void testDiscoveryResultBuilderWithBridge() {
assertThrows(IllegalArgumentException.class, () -> DiscoveryResultBuilder
.create(new ThingUID(THING_TYPE_UID, "otherThingId")).withBridge(BRIDGE_UID).build());
DiscoveryResult otherDiscoveryResult = DiscoveryResultBuilder
.create(new ThingUID(THING_TYPE_UID, "otherThingId")).withBridge(BRIDGE_UID).build();
assertThat(otherDiscoveryResult.getBridgeUID(), is(BRIDGE_UID));
}
@Test
@@ -228,7 +228,6 @@ public class ThingResource implements RESTResource {
@Operation(operationId = "createThingInRegistry", summary = "Creates a new thing and adds it to the registry.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "201", description = "Created", content = @Content(schema = @Schema(implementation = EnrichedThingDTO.class))),
@ApiResponse(responseCode = "400", description = "Thing uid does not match bridge uid."),
@ApiResponse(responseCode = "400", description = "A uid must be provided, if no binding can create a thing of this type."),
@ApiResponse(responseCode = "409", description = "A thing with the same uid already exists.") })
public Response create(
@@ -253,11 +252,6 @@ public class ThingResource implements RESTResource {
if (thingBean.bridgeUID != null) {
bridgeUID = new ThingUID(thingBean.bridgeUID);
if (thingUID != null && (!thingUID.getBindingId().equals(bridgeUID.getBindingId())
|| !thingUID.getBridgeIds().contains(bridgeUID.getId()))) {
return Response.status(Status.BAD_REQUEST)
.entity("Thing UID '" + thingUID + "' does not match bridge UID '" + bridgeUID + "'").build();
}
}
// turn the ThingDTO's configuration into a Configuration
@@ -104,6 +104,7 @@
<xs:complexType name="bridgeTypeRef">
<xs:attribute name="id" type="config-description:idRestrictionPattern" use="required"/>
<xs:attribute name="bindingId" type="config-description:idRestrictionPattern" use="optional"/>
</xs:complexType>
<xs:complexType name="channels">
@@ -12,6 +12,7 @@
*/
package org.openhab.core.thing.xml.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -19,6 +20,7 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.xml.util.ConverterAttributeMapValidator;
import org.openhab.core.config.core.xml.util.NodeAttributes;
import org.openhab.core.config.core.xml.util.NodeIterator;
import org.openhab.core.config.core.xml.util.NodeList;
import org.openhab.core.config.core.xml.util.NodeValue;
@@ -68,11 +70,30 @@ public class ThingTypeConverter extends AbstractDescriptionTypeConverter<ThingTy
Object nextNode = nodeIterator.next("supported-bridge-type-refs", false);
if (nextNode != null) {
String bindingID = (String) context.get("thing-descriptions.bindingId");
String thisBindingId = (String) context.get("thing-descriptions.bindingId");
String uidFormat = String.format("%s:%s", bindingID, "%s");
NodeList nodeList = (NodeList) nextNode;
List<NodeAttributes> nodes = (List<NodeAttributes>) nodeList.getList();
List<String> uids = new ArrayList<>(nodes.size());
return ((NodeList) nextNode).getAttributes("bridge-type-ref", "id", uidFormat);
for (NodeAttributes node : nodes) {
if ("bridge-type-ref".equals(node.getNodeName())) {
String id = node.getAttribute("id");
String bindingId = node.getAttribute("bindingId");
if (id == null) {
throw new ConversionException("Missing attribute 'id' in 'bridge-type-ref'!");
}
if (bindingId == null) {
bindingId = thisBindingId;
}
uids.add(String.format("%s:%s", bindingId, id));
} else {
throw new ConversionException("Invalid element in 'supported-bridge-type-refs'!");
}
}
return uids;
}
return null;
@@ -63,6 +63,10 @@ public class ThingDescriptionReaderTest {
assertThat(thingTypeXmlResult.label, is("HUE Lamp"));
assertThat(thingTypeXmlResult.description, is("My own great HUE Lamp."));
assertThat(thingTypeXmlResult.semanticEquipmentTag, is("LightBulb"));
List<String> supportedBridgeTypeUIDs = thingTypeXmlResult.getBuilder().build().getSupportedBridgeTypeUIDs();
assertThat(supportedBridgeTypeUIDs.size(), is(2));
assertTrue(supportedBridgeTypeUIDs.contains("hue:bridge"));
assertTrue(supportedBridgeTypeUIDs.contains("mqtt:broker"));
assertThat(channelGroupTypeXmlResults.size(), is(1));
@@ -7,6 +7,7 @@
<thing-type id="lamp">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref bindingId="mqtt" id="broker"/>
</supported-bridge-type-refs>
<label>HUE Lamp</label>