d42d51e9e10afa58a24641102db024c171b46d11
[ccsdk/oran.git] /
1 /*-
2  * ========================LICENSE_START=================================
3  * ONAP : ccsdk oran
4  * ======================================================================
5  * Copyright (C) 2019-2020 Nordix Foundation. All rights reserved.
6  * ======================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 package org.onap.ccsdk.oran.a1policymanagementservice.controllers.v2;
22
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25
26 import io.swagger.annotations.Api;
27 import io.swagger.annotations.ApiOperation;
28 import io.swagger.annotations.ApiParam;
29 import io.swagger.annotations.ApiResponse;
30 import io.swagger.annotations.ApiResponses;
31
32 import java.lang.invoke.MethodHandles;
33 import java.time.Instant;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.List;
37
38 import lombok.Getter;
39
40 import org.onap.ccsdk.oran.a1policymanagementservice.clients.A1ClientFactory;
41 import org.onap.ccsdk.oran.a1policymanagementservice.controllers.VoidResponse;
42 import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
43 import org.onap.ccsdk.oran.a1policymanagementservice.repository.ImmutablePolicy;
44 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock.LockType;
45 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
46 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
47 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyType;
48 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyTypes;
49 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Ric;
50 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Rics;
51 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
52 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.beans.factory.annotation.Autowired;
56 import org.springframework.http.HttpStatus;
57 import org.springframework.http.MediaType;
58 import org.springframework.http.ResponseEntity;
59 import org.springframework.web.bind.annotation.DeleteMapping;
60 import org.springframework.web.bind.annotation.GetMapping;
61 import org.springframework.web.bind.annotation.PutMapping;
62 import org.springframework.web.bind.annotation.RequestBody;
63 import org.springframework.web.bind.annotation.RequestParam;
64 import org.springframework.web.bind.annotation.RestController;
65 import org.springframework.web.reactive.function.client.WebClientResponseException;
66 import reactor.core.publisher.Mono;
67
68 @RestController("PolicyControllerV2")
69 @Api(tags = {Consts.V2_API_NAME}, description = "Policy management")
70 public class PolicyController {
71
72     public static class RejectionException extends Exception {
73         private static final long serialVersionUID = 1L;
74
75         @Getter
76         private final HttpStatus status;
77
78         public RejectionException(String message, HttpStatus status) {
79             super(message);
80             this.status = status;
81         }
82     }
83
84     @Autowired
85     private Rics rics;
86     @Autowired
87     private PolicyTypes policyTypes;
88     @Autowired
89     private Policies policies;
90     @Autowired
91     private A1ClientFactory a1ClientFactory;
92     @Autowired
93     private Services services;
94
95     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
96     private static Gson gson = new GsonBuilder() //
97         .serializeNulls() //
98         .create(); //
99
100     @GetMapping(path = Consts.V2_API_ROOT + "/policy-schemas", produces = MediaType.APPLICATION_JSON_VALUE)
101     @ApiOperation(value = "Returns policy type schema definitions")
102     @ApiResponses(
103         value = { //
104             @ApiResponse(code = 200, message = "Policy schemas", response = PolicySchemaList.class), //
105             @ApiResponse(code = 404, message = "Near-RT RIC is not found", response = ErrorResponse.ErrorInfo.class)})
106     public ResponseEntity<Object> getPolicySchemas( //
107         @ApiParam(
108             name = Consts.RIC_ID_PARAM,
109             required = false,
110             value = "The identity of the Near-RT RIC to get the definitions for.") //
111         @RequestParam(name = Consts.RIC_ID_PARAM, required = false) String ricId,
112         @ApiParam(
113             name = Consts.POLICY_TYPE_ID_PARAM,
114             required = true,
115             value = "The identity of the policy type to get the definition for. When this parameter is given, max one schema will be returned") //
116         @RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false) String policyTypeId) {
117         try {
118             Ric ric = ricId == null ? null : rics.getRic(ricId);
119             if (ric == null && policyTypeId == null) {
120                 Collection<PolicyType> types = this.policyTypes.getAll();
121                 return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
122             } else if (ric != null && policyTypeId != null) {
123                 synchronized (ric) {
124                     assertRicStateIdleSync(ric);
125                     Collection<PolicyType> types = new ArrayList<>();
126                     if (rics.getRic(ricId).isSupportingType(policyTypeId)) {
127                         types.add(policyTypes.getType(policyTypeId));
128                     }
129                     return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
130                 }
131             } else if (ric != null) {
132                 synchronized (ric) {
133                     assertRicStateIdleSync(ric);
134                     Collection<PolicyType> types = rics.getRic(ricId).getSupportedPolicyTypes();
135                     return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
136                 }
137             } else {
138                 Collection<PolicyType> types = new ArrayList<>();
139                 types.add(policyTypes.getType(policyTypeId));
140                 return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
141             }
142         } catch (ServiceException e) {
143             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
144         }
145     }
146
147     @GetMapping(path = Consts.V2_API_ROOT + "/policy-types", produces = MediaType.APPLICATION_JSON_VALUE)
148     @ApiOperation(value = "Query policy type identities", produces = MediaType.APPLICATION_JSON_VALUE)
149     @ApiResponses(
150         value = {@ApiResponse(code = 200, message = "Policy type IDs", response = PolicyTypeIdList.class),
151             @ApiResponse(code = 404, message = "Near-RT RIC is not found", response = ErrorResponse.ErrorInfo.class)})
152     public ResponseEntity<Object> getPolicyTypes( //
153         @ApiParam(
154             name = Consts.RIC_ID_PARAM,
155             required = false,
156             value = "The identity of the Near-RT RIC to get types for.") //
157         @RequestParam(name = Consts.RIC_ID_PARAM, required = false) String ricId) {
158         if (ricId == null) {
159             Collection<PolicyType> types = this.policyTypes.getAll();
160             return new ResponseEntity<>(toPolicyTypeIdsJson(types), HttpStatus.OK);
161         } else {
162             try {
163                 Collection<PolicyType> types = rics.getRic(ricId).getSupportedPolicyTypes();
164                 return new ResponseEntity<>(toPolicyTypeIdsJson(types), HttpStatus.OK);
165             } catch (ServiceException e) {
166                 return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
167             }
168         }
169     }
170
171     @GetMapping(path = Consts.V2_API_ROOT + "/policy", produces = MediaType.APPLICATION_JSON_VALUE)
172     @ApiOperation(value = "Returns a policy configuration") //
173     @ApiResponses(
174         value = { //
175             @ApiResponse(code = 200, message = "Policy found", response = JsonObject.class), //
176             @ApiResponse(code = 404, message = "Policy is not found", response = ErrorResponse.ErrorInfo.class)} //
177     )
178     public ResponseEntity<Object> getPolicy( //
179         @ApiParam(name = Consts.POLICY_ID_PARAM, required = true, value = "The identity of the policy instance.") //
180         @RequestParam(name = Consts.POLICY_ID_PARAM, required = true) String id) {
181         try {
182             Policy p = policies.getPolicy(id);
183             return new ResponseEntity<>(p.json(), HttpStatus.OK);
184         } catch (ServiceException e) {
185             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
186         }
187     }
188
189     @DeleteMapping(Consts.V2_API_ROOT + "/policy")
190     @ApiOperation(value = "Delete a policy")
191     @ApiResponses(
192         value = { //
193             @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
194             @ApiResponse(code = 204, message = "Policy deleted", response = VoidResponse.class),
195             @ApiResponse(code = 404, message = "Policy is not found", response = ErrorResponse.ErrorInfo.class),
196             @ApiResponse(
197                 code = 423,
198                 message = "Near-RT RIC is not operational",
199                 response = ErrorResponse.ErrorInfo.class)})
200     public Mono<ResponseEntity<Object>> deletePolicy( //
201         @ApiParam(name = Consts.POLICY_ID_PARAM, required = true, value = "The identity of the policy instance.") //
202         @RequestParam(name = Consts.POLICY_ID_PARAM, required = true) String id) {
203         try {
204             Policy policy = policies.getPolicy(id);
205             keepServiceAlive(policy.ownerServiceId());
206             Ric ric = policy.ric();
207             return ric.getLock().lock(LockType.SHARED) //
208                 .flatMap(notUsed -> assertRicStateIdle(ric)) //
209                 .flatMap(notUsed -> a1ClientFactory.createA1Client(policy.ric())) //
210                 .doOnNext(notUsed -> policies.remove(policy)) //
211                 .flatMap(client -> client.deletePolicy(policy)) //
212                 .doOnNext(notUsed -> ric.getLock().unlockBlocking()) //
213                 .doOnError(notUsed -> ric.getLock().unlockBlocking()) //
214                 .flatMap(notUsed -> Mono.just(new ResponseEntity<>(HttpStatus.NO_CONTENT)))
215                 .onErrorResume(this::handleException);
216         } catch (ServiceException e) {
217             return ErrorResponse.createMono(e, HttpStatus.NOT_FOUND);
218         }
219     }
220
221     @PutMapping(path = Consts.V2_API_ROOT + "/policy", produces = MediaType.APPLICATION_JSON_VALUE)
222     @ApiOperation(value = "Create or update a policy")
223     @ApiResponses(
224         value = { //
225             @ApiResponse(code = 201, message = "Policy created", response = VoidResponse.class), //
226             @ApiResponse(code = 200, message = "Policy updated", response = VoidResponse.class), //
227             @ApiResponse(
228                 code = 423,
229                 message = "Near-RT RIC is not operational",
230                 response = ErrorResponse.ErrorInfo.class), //
231             @ApiResponse(
232                 code = 404,
233                 message = "Near-RT RIC or policy type is not found",
234                 response = ErrorResponse.ErrorInfo.class) //
235         })
236     public Mono<ResponseEntity<Object>> putPolicy( //
237         @ApiParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false, value = "The identity of the policy type.") //
238         @RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false, defaultValue = "") String policyTypeId, //
239         @ApiParam(name = Consts.POLICY_ID_PARAM, required = true, value = "The identity of the policy instance.") //
240         @RequestParam(name = Consts.POLICY_ID_PARAM, required = true) String instanceId, //
241         @ApiParam(
242             name = Consts.RIC_ID_PARAM,
243             required = true,
244             value = "The identity of the Near-RT RIC where the policy will be " + //
245                 "created.") //
246         @RequestParam(name = Consts.RIC_ID_PARAM, required = true) String ricId, //
247         @ApiParam(
248             name = Consts.SERVICE_ID_PARAM,
249             required = true,
250             value = "The identity of the service creating the policy.") //
251         @RequestParam(name = Consts.SERVICE_ID_PARAM, required = true) String serviceId, //
252         @ApiParam(
253             name = Consts.TRANSIENT_PARAM,
254             required = false,
255             value = "If the policy is transient or not (boolean " + //
256                 "defaulted to false). A policy is transient if it will not be recreated in the Near-RT RIC " + //
257                 "when it has been lost (for instance due to a restart)") //
258         @RequestParam(name = Consts.TRANSIENT_PARAM, required = false, defaultValue = "false") boolean isTransient, //
259         @RequestBody Object jsonBody) {
260
261         String jsonString = gson.toJson(jsonBody);
262         Ric ric = rics.get(ricId);
263         PolicyType type = policyTypes.get(policyTypeId);
264         keepServiceAlive(serviceId);
265         if (ric == null || type == null) {
266             return ErrorResponse.createMono("Near-RT RIC or policy type not found", HttpStatus.NOT_FOUND);
267         }
268         Policy policy = ImmutablePolicy.builder() //
269             .id(instanceId) //
270             .json(jsonString) //
271             .type(type) //
272             .ric(ric) //
273             .ownerServiceId(serviceId) //
274             .lastModified(Instant.now()) //
275             .isTransient(isTransient) //
276             .build();
277
278         final boolean isCreate = this.policies.get(policy.id()) == null;
279
280         return ric.getLock().lock(LockType.SHARED) //
281             .flatMap(notUsed -> assertRicStateIdle(ric)) //
282             .flatMap(notUsed -> checkSupportedType(ric, type)) //
283             .flatMap(notUsed -> validateModifiedPolicy(policy)) //
284             .flatMap(notUsed -> a1ClientFactory.createA1Client(ric)) //
285             .flatMap(client -> client.putPolicy(policy)) //
286             .doOnNext(notUsed -> policies.put(policy)) //
287             .doOnNext(notUsed -> ric.getLock().unlockBlocking()) //
288             .doOnError(trowable -> ric.getLock().unlockBlocking()) //
289             .flatMap(notUsed -> Mono.just(new ResponseEntity<>(isCreate ? HttpStatus.CREATED : HttpStatus.OK))) //
290             .onErrorResume(this::handleException);
291     }
292
293     private Mono<ResponseEntity<Object>> handleException(Throwable throwable) {
294         if (throwable instanceof WebClientResponseException) {
295             WebClientResponseException e = (WebClientResponseException) throwable;
296             return ErrorResponse.createMono(e.getResponseBodyAsString(), e.getStatusCode());
297         } else if (throwable instanceof RejectionException) {
298             RejectionException e = (RejectionException) throwable;
299             return ErrorResponse.createMono(e.getMessage(), e.getStatus());
300         } else {
301             return ErrorResponse.createMono(throwable.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
302         }
303     }
304
305     private Mono<Object> validateModifiedPolicy(Policy policy) {
306         // Check that ric is not updated
307         Policy current = this.policies.get(policy.id());
308         if (current != null && !current.ric().id().equals(policy.ric().id())) {
309             RejectionException e = new RejectionException("Policy cannot change RIC, policyId: " + current.id() + //
310                 ", RIC ID: " + current.ric().id() + //
311                 ", new ID: " + policy.ric().id(), HttpStatus.CONFLICT);
312             logger.debug("Request rejected, {}", e.getMessage());
313             return Mono.error(e);
314         }
315         return Mono.just("{}");
316     }
317
318     private Mono<Object> checkSupportedType(Ric ric, PolicyType type) {
319         if (!ric.isSupportingType(type.id())) {
320             logger.debug("Request rejected, type not supported, RIC: {}", ric);
321             RejectionException e = new RejectionException("Type: " + type.id() + " not supported by RIC: " + ric.id(),
322                 HttpStatus.NOT_FOUND);
323             return Mono.error(e);
324         }
325         return Mono.just("{}");
326     }
327
328     private void assertRicStateIdleSync(Ric ric) throws ServiceException {
329         if (ric.getState() != Ric.RicState.AVAILABLE) {
330             throw new ServiceException("Near-RT RIC: " + ric.id() + " is " + ric.getState());
331         }
332     }
333
334     private Mono<Object> assertRicStateIdle(Ric ric) {
335         if (ric.getState() == Ric.RicState.AVAILABLE) {
336             return Mono.just("{}");
337         } else {
338             logger.debug("Request rejected Near-RT RIC not IDLE, ric: {}", ric);
339             RejectionException e = new RejectionException(
340                 "Near-RT RIC: is not operational, id: " + ric.id() + ", state: " + ric.getState(), HttpStatus.LOCKED);
341             return Mono.error(e);
342         }
343     }
344
345     static final String GET_POLICIES_QUERY_DETAILS =
346         "Returns a list of A1 policies matching given search criteria. <br>" //
347             + "If several query parameters are defined, the policies matching all conditions are returned.";
348
349     @GetMapping(path = Consts.V2_API_ROOT + "/policies", produces = MediaType.APPLICATION_JSON_VALUE)
350     @ApiOperation(value = "Query for existing A1 policies", notes = GET_POLICIES_QUERY_DETAILS)
351     @ApiResponses(
352         value = { //
353             @ApiResponse(code = 200, message = "Policies", response = PolicyInfoList.class),
354             @ApiResponse(
355                 code = 404,
356                 message = "Near-RT RIC, policy type or service not found",
357                 response = ErrorResponse.ErrorInfo.class)})
358     public ResponseEntity<Object> getPolicies( //
359         @ApiParam(
360             name = Consts.POLICY_TYPE_ID_PARAM,
361             required = false,
362             value = "The identity of the policy type to get policies for.") //
363         @RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false) String type, //
364         @ApiParam(
365             name = Consts.RIC_ID_PARAM,
366             required = false,
367             value = "The identity of the Near-RT RIC to get policies for.") //
368         @RequestParam(name = Consts.RIC_ID_PARAM, required = false) String ric, //
369         @ApiParam(
370             name = Consts.SERVICE_ID_PARAM,
371             required = false,
372             value = "The identity of the service to get policies for.") //
373         @RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String service) //
374     {
375         if ((type != null && this.policyTypes.get(type) == null)) {
376             return ErrorResponse.create("Policy type not found", HttpStatus.NOT_FOUND);
377         }
378         if ((ric != null && this.rics.get(ric) == null)) {
379             return ErrorResponse.create("Near-RT RIC not found", HttpStatus.NOT_FOUND);
380         }
381
382         String filteredPolicies = policiesToJson(filter(type, ric, service));
383         return new ResponseEntity<>(filteredPolicies, HttpStatus.OK);
384     }
385
386     @GetMapping(path = Consts.V2_API_ROOT + "/policy-ids", produces = MediaType.APPLICATION_JSON_VALUE)
387     @ApiOperation(value = "Query policies, only policy identities are returned", notes = GET_POLICIES_QUERY_DETAILS)
388     @ApiResponses(
389         value = { //
390             @ApiResponse(code = 200, message = "Policy identities", response = PolicyIdList.class),
391             @ApiResponse(
392                 code = 404,
393                 message = "Near-RT RIC or type not found",
394                 response = ErrorResponse.ErrorInfo.class)})
395     public ResponseEntity<Object> getPolicyIds( //
396         @ApiParam(
397             name = Consts.POLICY_TYPE_ID_PARAM,
398             required = false,
399             value = "The identity of the policy type to get policies for.") //
400         @RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false) String policyTypeId, //
401         @ApiParam(
402             name = Consts.RIC_ID_PARAM,
403             required = false,
404             value = "The identity of the Near-RT RIC to get policies for.") //
405         @RequestParam(name = Consts.RIC_ID_PARAM, required = false) String ricId, //
406         @ApiParam(
407             name = Consts.SERVICE_ID_PARAM,
408             required = false,
409             value = "The identity of the service to get policies for.") //
410         @RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String serviceId) //
411     {
412         if ((policyTypeId != null && this.policyTypes.get(policyTypeId) == null)) {
413             return ErrorResponse.create("Policy type not found", HttpStatus.NOT_FOUND);
414         }
415         if ((ricId != null && this.rics.get(ricId) == null)) {
416             return ErrorResponse.create("Near-RT RIC not found", HttpStatus.NOT_FOUND);
417         }
418
419         String policyIdsJson = toPolicyIdsJson(filter(policyTypeId, ricId, serviceId));
420         return new ResponseEntity<>(policyIdsJson, HttpStatus.OK);
421     }
422
423     @GetMapping(path = Consts.V2_API_ROOT + "/policy-status", produces = MediaType.APPLICATION_JSON_VALUE)
424     @ApiOperation(value = "Returns a policy status") //
425     @ApiResponses(
426         value = { //
427             @ApiResponse(code = 200, message = "Policy status", response = JsonObject.class), //
428             @ApiResponse(code = 404, message = "Policy is not found", response = ErrorResponse.ErrorInfo.class)} //
429     )
430     public Mono<ResponseEntity<Object>> getPolicyStatus( //
431         @ApiParam(name = Consts.POLICY_ID_PARAM, required = true, value = "The identity of the policy.") //
432         @RequestParam(name = Consts.POLICY_ID_PARAM, required = true) String policyId) {
433         try {
434             Policy policy = policies.getPolicy(policyId);
435
436             return a1ClientFactory.createA1Client(policy.ric()) //
437                 .flatMap(client -> client.getPolicyStatus(policy)) //
438                 .flatMap(status -> Mono.just(new ResponseEntity<>((Object) status, HttpStatus.OK)))
439                 .onErrorResume(this::handleException);
440         } catch (ServiceException e) {
441             return ErrorResponse.createMono(e, HttpStatus.NOT_FOUND);
442         }
443     }
444
445     private void keepServiceAlive(String name) {
446         Service s = this.services.get(name);
447         if (s != null) {
448             s.keepAlive();
449         }
450     }
451
452     private boolean include(String filter, String value) {
453         return filter == null || value.equals(filter);
454     }
455
456     private Collection<Policy> filter(Collection<Policy> collection, String type, String ric, String service) {
457         if (type == null && ric == null && service == null) {
458             return collection;
459         }
460         List<Policy> filtered = new ArrayList<>();
461         for (Policy p : collection) {
462             if (include(type, p.type().id()) && include(ric, p.ric().id()) && include(service, p.ownerServiceId())) {
463                 filtered.add(p);
464             }
465         }
466         return filtered;
467     }
468
469     private Collection<Policy> filter(String type, String ric, String service) {
470         if (type != null) {
471             return filter(policies.getForType(type), null, ric, service);
472         } else if (service != null) {
473             return filter(policies.getForService(service), type, ric, null);
474         } else if (ric != null) {
475             return filter(policies.getForRic(ric), type, null, service);
476         } else {
477             return policies.getAll();
478         }
479     }
480
481     private String policiesToJson(Collection<Policy> policies) {
482         List<PolicyInfo> v = new ArrayList<>(policies.size());
483         for (Policy p : policies) {
484             PolicyInfo policyInfo = new PolicyInfo();
485             policyInfo.policyId = p.id();
486             policyInfo.policyData = fromJson(p.json());
487             policyInfo.ricId = p.ric().id();
488             policyInfo.policyTypeId = p.type().id();
489             policyInfo.serviceId = p.ownerServiceId();
490             policyInfo.lastModified = p.lastModified().toString();
491             if (!policyInfo.validate()) {
492                 logger.error("BUG, all fields must be set");
493             }
494             v.add(policyInfo);
495         }
496         PolicyInfoList list = new PolicyInfoList(v);
497         return gson.toJson(list);
498     }
499
500     private Object fromJson(String jsonStr) {
501         return gson.fromJson(jsonStr, Object.class);
502     }
503
504     private String toPolicyTypeSchemasJson(Collection<PolicyType> types) {
505
506         Collection<String> schemas = new ArrayList<>();
507         for (PolicyType t : types) {
508             schemas.add(t.schema());
509         }
510         PolicySchemaList res = new PolicySchemaList(schemas);
511         return gson.toJson(res);
512     }
513
514     private String toPolicyTypeIdsJson(Collection<PolicyType> types) {
515         List<String> v = new ArrayList<>(types.size());
516         for (PolicyType t : types) {
517             v.add(t.id());
518         }
519         PolicyTypeIdList ids = new PolicyTypeIdList(v);
520         return gson.toJson(ids);
521     }
522
523     private String toPolicyIdsJson(Collection<Policy> policies) {
524         List<String> v = new ArrayList<>(policies.size());
525         for (Policy p : policies) {
526             v.add(p.id());
527         }
528         return gson.toJson(new PolicyIdList(v));
529     }
530
531 }