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,48 @@ 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
+ private List <Item > sortItems (Collection <Item > items ) {
1083
+ return items .stream ().sorted ((item1 , item2 ) -> {
1084
+ if (item1 .getName ().equals (item2 .getName ())) {
1085
+ return 0 ;
1086
+ } else if (isAncestorGroupOf (item1 , item2 )) {
1087
+ return -1 ;
1088
+ } else if (isAncestorGroupOf (item2 , item1 )) {
1089
+ return 1 ;
1090
+ } else if (item1 instanceof GroupItem && !(item2 instanceof GroupItem )) {
1091
+ return -1 ;
1092
+ } else if (item2 instanceof GroupItem && !(item1 instanceof GroupItem )) {
1093
+ return 1 ;
1094
+ } else {
1095
+ return item1 .getName ().compareTo (item2 .getName ());
1096
+ }
1097
+ }).collect (Collectors .toList ());
1098
+ }
1099
+
1100
+ private boolean isAncestorGroupOf (Item item1 , Item item2 ) {
1101
+ if (!item1 .getName ().equals (item2 .getName ()) && item1 instanceof GroupItem group ) {
1102
+ if (item2 instanceof GroupItem ) {
1103
+ List <Item > items = new ArrayList <>();
1104
+ fillGroupTree (items , group );
1105
+ return items .contains (item2 );
1106
+ } else {
1107
+ return group .getAllMembers ().contains (item2 );
1108
+ }
1109
+ }
1110
+ return false ;
1111
+ }
1112
+
1113
+ private void fillGroupTree (List <Item > items , Item item ) {
1114
+ if (!items .contains (item )) {
1115
+ items .add (item );
1116
+ if (item instanceof GroupItem group ) {
1117
+ for (Item member : group .getMembers ()) {
1118
+ if (member instanceof GroupItem groupMember ) {
1119
+ fillGroupTree (items , groupMember );
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1009
1125
}
0 commit comments