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,66 @@ 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
+ * - first the groups, with ancestors before its children and then sorted by names.
1085
+ * - Then all items not linked to a channel, sorted by names.
1086
+ * - Finally all items linked to a channel, sorted first by binding names and then by item names.
1087
+ */
1088
+ private List <Item > sortItems (Collection <Item > items ) {
1089
+ return items .stream ().sorted ((item1 , item2 ) -> {
1090
+ String name1 = item1 .getName ();
1091
+ String name2 = item2 .getName ();
1092
+ if (name1 .equals (name2 )) {
1093
+ return 0 ;
1094
+ } else if (item1 instanceof GroupItem && !(item2 instanceof GroupItem )) {
1095
+ return -1 ;
1096
+ } else if (!(item1 instanceof GroupItem ) && item2 instanceof GroupItem ) {
1097
+ return 1 ;
1098
+ } else if (item1 instanceof GroupItem group1 && item2 instanceof GroupItem group2
1099
+ && isAncestorGroupOf (group1 , group2 )) {
1100
+ return -1 ;
1101
+ } else if (item1 instanceof GroupItem group1 && item2 instanceof GroupItem group2
1102
+ && isAncestorGroupOf (group2 , group1 )) {
1103
+ return 1 ;
1104
+ } else if (!(item1 instanceof GroupItem ) && !(item2 instanceof GroupItem )) {
1105
+ Set <ItemChannelLink > channelLinks1 = itemChannelLinkRegistry .getLinks (name1 );
1106
+ String binding1 = channelLinks1 .isEmpty () ? null
1107
+ : channelLinks1 .iterator ().next ().getLinkedUID ().getBindingId ();
1108
+ Set <ItemChannelLink > channelLinks2 = itemChannelLinkRegistry .getLinks (name2 );
1109
+ String binding2 = channelLinks2 .isEmpty () ? null
1110
+ : channelLinks2 .iterator ().next ().getLinkedUID ().getBindingId ();
1111
+
1112
+ if (binding1 == null && binding2 != null ) {
1113
+ return -1 ;
1114
+ } else if (binding1 != null && binding2 == null ) {
1115
+ return 1 ;
1116
+ } else if (binding1 != null && binding2 != null && !binding1 .equals (binding2 )) {
1117
+ return binding1 .compareTo (binding2 );
1118
+ }
1119
+ }
1120
+ return name1 .compareTo (name2 );
1121
+ }).collect (Collectors .toList ());
1122
+ }
1123
+
1124
+ private boolean isAncestorGroupOf (GroupItem group1 , GroupItem group2 ) {
1125
+ if (!group1 .getName ().equals (group2 .getName ())) {
1126
+ Set <GroupItem > groups = new HashSet <>();
1127
+ fillGroupTree (groups , group1 );
1128
+ return groups .contains (group2 );
1129
+ }
1130
+ return false ;
1131
+ }
1132
+
1133
+ private void fillGroupTree (Set <GroupItem > groups , GroupItem group ) {
1134
+ if (!groups .contains (group )) {
1135
+ groups .add (group );
1136
+ for (Item member : group .getMembers ()) {
1137
+ if (member instanceof GroupItem groupMember ) {
1138
+ fillGroupTree (groups , groupMember );
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1009
1143
}
0 commit comments