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 ;
137
143
* @author Stefan Triller - Added bulk item add method
138
144
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
139
145
* @author Wouter Born - Migrated to OpenAPI annotations
146
+ * @author Laurent Garnier - Added API to generate file syntax
140
147
*/
141
148
@ Component
142
149
@ JaxrsResource
@@ -182,7 +189,9 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context
182
189
private final MetadataRegistry metadataRegistry ;
183
190
private final MetadataSelectorMatcher metadataSelectorMatcher ;
184
191
private final SemanticTagRegistry semanticTagRegistry ;
192
+ private final ItemChannelLinkRegistry itemChannelLinkRegistry ;
185
193
private final TimeZoneProvider timeZoneProvider ;
194
+ private final Map <String , ItemSyntaxGenerator > itemSyntaxGenerators = new ConcurrentHashMap <>();
186
195
187
196
private final RegistryChangedRunnableListener <Item > resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener <>(
188
197
() -> lastModified = null );
@@ -202,6 +211,7 @@ public ItemResource(//
202
211
final @ Reference MetadataRegistry metadataRegistry ,
203
212
final @ Reference MetadataSelectorMatcher metadataSelectorMatcher ,
204
213
final @ Reference SemanticTagRegistry semanticTagRegistry ,
214
+ final @ Reference ItemChannelLinkRegistry itemChannelLinkRegistry ,
205
215
final @ Reference TimeZoneProvider timeZoneProvider ) {
206
216
this .dtoMapper = dtoMapper ;
207
217
this .eventPublisher = eventPublisher ;
@@ -212,6 +222,7 @@ public ItemResource(//
212
222
this .metadataRegistry = metadataRegistry ;
213
223
this .metadataSelectorMatcher = metadataSelectorMatcher ;
214
224
this .semanticTagRegistry = semanticTagRegistry ;
225
+ this .itemChannelLinkRegistry = itemChannelLinkRegistry ;
215
226
this .timeZoneProvider = timeZoneProvider ;
216
227
217
228
this .itemRegistry .addRegistryChangeListener (resetLastModifiedItemChangeListener );
@@ -224,6 +235,15 @@ void deactivate() {
224
235
this .metadataRegistry .removeRegistryChangeListener (resetLastModifiedMetadataChangeListener );
225
236
}
226
237
238
+ @ Reference (policy = ReferencePolicy .DYNAMIC , cardinality = ReferenceCardinality .MULTIPLE )
239
+ protected void addItemSyntaxGenerator (ItemSyntaxGenerator itemSyntaxGenerator ) {
240
+ itemSyntaxGenerators .put (itemSyntaxGenerator .getFormat (), itemSyntaxGenerator );
241
+ }
242
+
243
+ protected void removeItemSyntaxGenerator (ItemSyntaxGenerator itemSyntaxGenerator ) {
244
+ itemSyntaxGenerators .remove (itemSyntaxGenerator .getFormat ());
245
+ }
246
+
227
247
private UriBuilder uriBuilder (final UriInfo uriInfo , final HttpHeaders httpHeaders ) {
228
248
final UriBuilder uriBuilder = uriInfo .getBaseUriBuilder ().path (PATH_ITEMS ).path ("{itemName}" );
229
249
respectForwarded (uriBuilder , httpHeaders );
@@ -901,6 +921,58 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H
901
921
return JSONResponse .createResponse (Status .OK , dto , null );
902
922
}
903
923
924
+ @ GET
925
+ @ RolesAllowed ({ Role .ADMIN })
926
+ @ Path ("/filesyntax" )
927
+ @ Produces (MediaType .TEXT_PLAIN )
928
+ @ Operation (operationId = "generateSyntaxForAllItems" , summary = "Generate file syntax for all items." , security = {
929
+ @ SecurityRequirement (name = "oauth2" , scopes = { "admin" }) }, responses = {
930
+ @ ApiResponse (responseCode = "200" , description = "OK" , content = @ Content (schema = @ Schema (implementation = String .class ))),
931
+ @ ApiResponse (responseCode = "400" , description = "Unsupported syntax format." ) })
932
+ public Response generateSyntaxForAllItems (
933
+ @ HeaderParam (HttpHeaders .ACCEPT_LANGUAGE ) @ Parameter (description = "language" ) @ Nullable String language ,
934
+ @ DefaultValue ("DSL" ) @ QueryParam ("format" ) @ Parameter (description = "syntax format" ) String format ) {
935
+ ItemSyntaxGenerator generator = itemSyntaxGenerators .get (format );
936
+ if (generator == null ) {
937
+ String message = "No syntax available for format " + format + "!" ;
938
+ return Response .status (Response .Status .BAD_REQUEST ).entity (message ).build ();
939
+ }
940
+ return Response .ok (generator .generateSyntax (sortItems (itemRegistry .getAll ()), itemChannelLinkRegistry .getAll (),
941
+ metadataRegistry .getAll ())).build ();
942
+ }
943
+
944
+ @ GET
945
+ @ RolesAllowed ({ Role .ADMIN })
946
+ @ Path ("/{itemname: [a-zA-Z_0-9]+}/filesyntax" )
947
+ @ Produces (MediaType .TEXT_PLAIN )
948
+ @ Operation (operationId = "generateSyntaxForItem" , summary = "Generate file syntax for an item." , security = {
949
+ @ SecurityRequirement (name = "oauth2" , scopes = { "admin" }) }, responses = {
950
+ @ ApiResponse (responseCode = "200" , description = "OK" , content = @ Content (schema = @ Schema (implementation = String .class ))),
951
+ @ ApiResponse (responseCode = "400" , description = "Unsupported syntax format." ),
952
+ @ ApiResponse (responseCode = "404" , description = "Item not found." ) })
953
+ public Response generateSyntaxForItem (
954
+ @ HeaderParam (HttpHeaders .ACCEPT_LANGUAGE ) @ Parameter (description = "language" ) @ Nullable String language ,
955
+ @ PathParam ("itemname" ) @ Parameter (description = "item name" ) String itemname ,
956
+ @ DefaultValue ("DSL" ) @ QueryParam ("format" ) @ Parameter (description = "syntax format" ) String format ) {
957
+ ItemSyntaxGenerator generator = itemSyntaxGenerators .get (format );
958
+ if (generator == null ) {
959
+ String message = "No syntax available for format " + format + "!" ;
960
+ return Response .status (Response .Status .BAD_REQUEST ).entity (message ).build ();
961
+ }
962
+
963
+ Item item = getItem (itemname );
964
+ if (item == null ) {
965
+ String message = "Item " + itemname + " does not exist!" ;
966
+ return Response .status (Response .Status .NOT_FOUND ).entity (message ).build ();
967
+ }
968
+
969
+ Set <ItemChannelLink > channelLinks = itemChannelLinkRegistry .getLinks (itemname );
970
+ Set <Metadata > metadata = metadataRegistry .getAll ().stream ()
971
+ .filter (md -> md .getUID ().getItemName ().equals (itemname )).collect (Collectors .toSet ());
972
+
973
+ return Response .ok (generator .generateSyntax (List .of (item ), channelLinks , metadata )).build ();
974
+ }
975
+
904
976
private JsonObject buildStatusObject (String itemName , String status , @ Nullable String message ) {
905
977
JsonObject jo = new JsonObject ();
906
978
jo .addProperty ("name" , itemName );
@@ -1006,4 +1078,65 @@ private void addMetadata(EnrichedItemDTO dto, Set<String> namespaces, @Nullable
1006
1078
private boolean isEditable (String itemName ) {
1007
1079
return managedItemProvider .get (itemName ) != null ;
1008
1080
}
1081
+
1082
+ /*
1083
+ * Sort the items in such a way:
1084
+ * - group items are before non group items
1085
+ * - group items are sorted to have as much as possible ancestors before their children
1086
+ * - items not linked to a channel are before items linked to a channel
1087
+ * - items linked to a channel are grouped by thing UID
1088
+ * - items linked to the same thing UID are sorted by item name
1089
+ */
1090
+ private List <Item > sortItems (Collection <Item > items ) {
1091
+ List <Item > groups = items .stream ().filter (item -> item instanceof GroupItem ).sorted ((item1 , item2 ) -> {
1092
+ return item1 .getName ().compareTo (item2 .getName ());
1093
+ }).collect (Collectors .toList ());
1094
+
1095
+ List <Item > topGroups = groups .stream ().filter (group -> group .getGroupNames ().isEmpty ())
1096
+ .sorted ((group1 , group2 ) -> {
1097
+ return group1 .getName ().compareTo (group2 .getName ());
1098
+ }).collect (Collectors .toList ());
1099
+
1100
+ List <Item > groupTree = new ArrayList <>();
1101
+ for (Item group : topGroups ) {
1102
+ fillGroupTree (groupTree , group );
1103
+ }
1104
+
1105
+ if (groupTree .size () != groups .size ()) {
1106
+ logger .warn ("Something want wrong when sorting groups; failback to a sort by name." );
1107
+ groupTree = groups ;
1108
+ }
1109
+
1110
+ List <Item > nonGroups = items .stream ().filter (item -> !(item instanceof GroupItem )).sorted ((item1 , item2 ) -> {
1111
+ Set <ItemChannelLink > channelLinks1 = itemChannelLinkRegistry .getLinks (item1 .getName ());
1112
+ String thingUID1 = channelLinks1 .isEmpty () ? null
1113
+ : channelLinks1 .iterator ().next ().getLinkedUID ().getThingUID ().getAsString ();
1114
+ Set <ItemChannelLink > channelLinks2 = itemChannelLinkRegistry .getLinks (item2 .getName ());
1115
+ String thingUID2 = channelLinks2 .isEmpty () ? null
1116
+ : channelLinks2 .iterator ().next ().getLinkedUID ().getThingUID ().getAsString ();
1117
+
1118
+ if (thingUID1 == null && thingUID2 != null ) {
1119
+ return -1 ;
1120
+ } else if (thingUID1 != null && thingUID2 == null ) {
1121
+ return 1 ;
1122
+ } else if (thingUID1 != null && thingUID2 != null && !thingUID1 .equals (thingUID2 )) {
1123
+ return thingUID1 .compareTo (thingUID2 );
1124
+ }
1125
+ return item1 .getName ().compareTo (item2 .getName ());
1126
+ }).collect (Collectors .toList ());
1127
+
1128
+ return Stream .of (groupTree , nonGroups ).flatMap (List ::stream ).collect (Collectors .toList ());
1129
+ }
1130
+
1131
+ private void fillGroupTree (List <Item > groups , Item item ) {
1132
+ if (item instanceof GroupItem group && !groups .contains (group )) {
1133
+ groups .add (group );
1134
+ List <Item > members = group .getMembers ().stream ().sorted ((member1 , member2 ) -> {
1135
+ return member1 .getName ().compareTo (member2 .getName ());
1136
+ }).collect (Collectors .toList ());
1137
+ for (Item member : members ) {
1138
+ fillGroupTree (groups , member );
1139
+ }
1140
+ }
1141
+ }
1009
1142
}
0 commit comments