27
27
import java .util .Objects ;
28
28
import java .util .Optional ;
29
29
import java .util .Set ;
30
+ import java .util .concurrent .ConcurrentHashMap ;
30
31
import java .util .function .Predicate ;
31
32
import java .util .stream .Collectors ;
32
33
import java .util .stream .Stream ;
89
90
import org .openhab .core .library .types .UpDownType ;
90
91
import org .openhab .core .semantics .SemanticTagRegistry ;
91
92
import org .openhab .core .semantics .SemanticsPredicates ;
93
+ import org .openhab .core .thing .link .ItemChannelLink ;
94
+ import org .openhab .core .thing .link .ItemChannelLinkRegistry ;
95
+ import org .openhab .core .thing .syntaxgenerator .ItemSyntaxGenerator ;
92
96
import org .openhab .core .types .Command ;
93
97
import org .openhab .core .types .State ;
94
98
import org .openhab .core .types .TypeParser ;
95
99
import org .osgi .service .component .annotations .Activate ;
96
100
import org .osgi .service .component .annotations .Component ;
97
101
import org .osgi .service .component .annotations .Deactivate ;
98
102
import org .osgi .service .component .annotations .Reference ;
103
+ import org .osgi .service .component .annotations .ReferenceCardinality ;
104
+ import org .osgi .service .component .annotations .ReferencePolicy ;
99
105
import org .osgi .service .jaxrs .whiteboard .JaxrsWhiteboardConstants ;
100
106
import org .osgi .service .jaxrs .whiteboard .propertytypes .JSONRequired ;
101
107
import org .osgi .service .jaxrs .whiteboard .propertytypes .JaxrsApplicationSelect ;
@@ -182,7 +188,9 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context
182
188
private final MetadataRegistry metadataRegistry ;
183
189
private final MetadataSelectorMatcher metadataSelectorMatcher ;
184
190
private final SemanticTagRegistry semanticTagRegistry ;
191
+ private final ItemChannelLinkRegistry itemChannelLinkRegistry ;
185
192
private final TimeZoneProvider timeZoneProvider ;
193
+ private final Map <String , ItemSyntaxGenerator > itemSyntaxGenerators = new ConcurrentHashMap <>();
186
194
187
195
private final RegistryChangedRunnableListener <Item > resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener <>(
188
196
() -> lastModified = null );
@@ -202,6 +210,7 @@ public ItemResource(//
202
210
final @ Reference MetadataRegistry metadataRegistry ,
203
211
final @ Reference MetadataSelectorMatcher metadataSelectorMatcher ,
204
212
final @ Reference SemanticTagRegistry semanticTagRegistry ,
213
+ final @ Reference ItemChannelLinkRegistry itemChannelLinkRegistry ,
205
214
final @ Reference TimeZoneProvider timeZoneProvider ) {
206
215
this .dtoMapper = dtoMapper ;
207
216
this .eventPublisher = eventPublisher ;
@@ -212,6 +221,7 @@ public ItemResource(//
212
221
this .metadataRegistry = metadataRegistry ;
213
222
this .metadataSelectorMatcher = metadataSelectorMatcher ;
214
223
this .semanticTagRegistry = semanticTagRegistry ;
224
+ this .itemChannelLinkRegistry = itemChannelLinkRegistry ;
215
225
this .timeZoneProvider = timeZoneProvider ;
216
226
217
227
this .itemRegistry .addRegistryChangeListener (resetLastModifiedItemChangeListener );
@@ -224,6 +234,15 @@ void deactivate() {
224
234
this .metadataRegistry .removeRegistryChangeListener (resetLastModifiedMetadataChangeListener );
225
235
}
226
236
237
+ @ Reference (policy = ReferencePolicy .DYNAMIC , cardinality = ReferenceCardinality .MULTIPLE )
238
+ protected void addItemSyntaxGenerator (ItemSyntaxGenerator itemSyntaxGenerator ) {
239
+ itemSyntaxGenerators .put (itemSyntaxGenerator .getFormat (), itemSyntaxGenerator );
240
+ }
241
+
242
+ protected void removeItemSyntaxGenerator (ItemSyntaxGenerator itemSyntaxGenerator ) {
243
+ itemSyntaxGenerators .remove (itemSyntaxGenerator .getFormat ());
244
+ }
245
+
227
246
private UriBuilder uriBuilder (final UriInfo uriInfo , final HttpHeaders httpHeaders ) {
228
247
final UriBuilder uriBuilder = uriInfo .getBaseUriBuilder ().path (PATH_ITEMS ).path ("{itemName}" );
229
248
respectForwarded (uriBuilder , httpHeaders );
@@ -901,6 +920,58 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H
901
920
return JSONResponse .createResponse (Status .OK , dto , null );
902
921
}
903
922
923
+ @ GET
924
+ @ RolesAllowed ({ Role .ADMIN })
925
+ @ Path ("/filesyntax" )
926
+ @ Produces (MediaType .TEXT_PLAIN )
927
+ @ Operation (operationId = "generateSyntaxForAllItems" , summary = "Generate file syntax for all items." , security = {
928
+ @ SecurityRequirement (name = "oauth2" , scopes = { "admin" }) }, responses = {
929
+ @ ApiResponse (responseCode = "200" , description = "OK" , content = @ Content (schema = @ Schema (implementation = String .class ))),
930
+ @ ApiResponse (responseCode = "400" , description = "Unsupported syntax format." ) })
931
+ public Response generateSyntaxForAllItems (
932
+ @ HeaderParam (HttpHeaders .ACCEPT_LANGUAGE ) @ Parameter (description = "language" ) @ Nullable String language ,
933
+ @ DefaultValue ("DSL" ) @ QueryParam ("format" ) @ Parameter (description = "syntax format" ) String format ) {
934
+ ItemSyntaxGenerator generator = itemSyntaxGenerators .get (format );
935
+ if (generator == null ) {
936
+ String message = "No syntax available for format " + format + "!" ;
937
+ return Response .status (Response .Status .BAD_REQUEST ).entity (message ).build ();
938
+ }
939
+ return Response .ok (generator .generateSyntax (sortItems (itemRegistry .getAll ()), itemChannelLinkRegistry .getAll (),
940
+ metadataRegistry .getAll ())).build ();
941
+ }
942
+
943
+ @ GET
944
+ @ RolesAllowed ({ Role .ADMIN })
945
+ @ Path ("/{itemname: [a-zA-Z_0-9]+}/filesyntax" )
946
+ @ Produces (MediaType .TEXT_PLAIN )
947
+ @ Operation (operationId = "generateSyntaxForItem" , summary = "Generate file syntax for an item." , security = {
948
+ @ SecurityRequirement (name = "oauth2" , scopes = { "admin" }) }, responses = {
949
+ @ ApiResponse (responseCode = "200" , description = "OK" , content = @ Content (schema = @ Schema (implementation = String .class ))),
950
+ @ ApiResponse (responseCode = "400" , description = "Unsupported syntax format." ),
951
+ @ ApiResponse (responseCode = "404" , description = "Item not found." ) })
952
+ public Response generateSyntaxForItem (
953
+ @ HeaderParam (HttpHeaders .ACCEPT_LANGUAGE ) @ Parameter (description = "language" ) @ Nullable String language ,
954
+ @ PathParam ("itemname" ) @ Parameter (description = "item name" ) String itemname ,
955
+ @ DefaultValue ("DSL" ) @ QueryParam ("format" ) @ Parameter (description = "syntax format" ) String format ) {
956
+ ItemSyntaxGenerator generator = itemSyntaxGenerators .get (format );
957
+ if (generator == null ) {
958
+ String message = "No syntax available for format " + format + "!" ;
959
+ return Response .status (Response .Status .BAD_REQUEST ).entity (message ).build ();
960
+ }
961
+
962
+ Item item = getItem (itemname );
963
+ if (item == null ) {
964
+ String message = "Item " + itemname + " does not exist!" ;
965
+ return Response .status (Response .Status .NOT_FOUND ).entity (message ).build ();
966
+ }
967
+
968
+ Set <ItemChannelLink > channelLinks = itemChannelLinkRegistry .getLinks (itemname );
969
+ Set <Metadata > metadata = metadataRegistry .getAll ().stream ()
970
+ .filter (md -> md .getUID ().getItemName ().equals (itemname )).collect (Collectors .toSet ());
971
+
972
+ return Response .ok (generator .generateSyntax (List .of (item ), channelLinks , metadata )).build ();
973
+ }
974
+
904
975
private JsonObject buildStatusObject (String itemName , String status , @ Nullable String message ) {
905
976
JsonObject jo = new JsonObject ();
906
977
jo .addProperty ("name" , itemName );
@@ -1006,4 +1077,48 @@ private void addMetadata(EnrichedItemDTO dto, Set<String> namespaces, @Nullable
1006
1077
private boolean isEditable (String itemName ) {
1007
1078
return managedItemProvider .get (itemName ) != null ;
1008
1079
}
1080
+
1081
+ private List <Item > sortItems (Collection <Item > items ) {
1082
+ return items .stream ().sorted ((item1 , item2 ) -> {
1083
+ if (item1 .getName ().equals (item2 .getName ())) {
1084
+ return 0 ;
1085
+ } else if (isAncestorGroupOf (item1 , item2 )) {
1086
+ return -1 ;
1087
+ } else if (isAncestorGroupOf (item2 , item1 )) {
1088
+ return 1 ;
1089
+ } else if (item1 instanceof GroupItem && !(item2 instanceof GroupItem )) {
1090
+ return -1 ;
1091
+ } else if (item2 instanceof GroupItem && !(item1 instanceof GroupItem )) {
1092
+ return 1 ;
1093
+ } else {
1094
+ return item1 .getName ().compareTo (item2 .getName ());
1095
+ }
1096
+ }).collect (Collectors .toList ());
1097
+ }
1098
+
1099
+ private boolean isAncestorGroupOf (Item item1 , Item item2 ) {
1100
+ if (!item1 .getName ().equals (item2 .getName ()) && item1 instanceof GroupItem group ) {
1101
+ if (item2 instanceof GroupItem ) {
1102
+ List <Item > items = new ArrayList <>();
1103
+ fillGroupTree (items , group );
1104
+ return items .contains (item2 );
1105
+ } else {
1106
+ return group .getAllMembers ().contains (item2 );
1107
+ }
1108
+ }
1109
+ return false ;
1110
+ }
1111
+
1112
+ private void fillGroupTree (List <Item > items , Item item ) {
1113
+ if (!items .contains (item )) {
1114
+ items .add (item );
1115
+ if (item instanceof GroupItem group ) {
1116
+ for (Item member : group .getMembers ()) {
1117
+ if (member instanceof GroupItem groupMember ) {
1118
+ fillGroupTree (items , groupMember );
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1009
1124
}
0 commit comments