Convert neutron northbound classes to unix line delimiters
[controller.git] / opendaylight / northbound / networkconfiguration / neutron / src / main / java / org / opendaylight / controller / networkconfig / neutron / northbound / NeutronRoutersNorthbound.java
1 /*
2  * Copyright IBM Corporation, 2013.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.networkconfig.neutron.northbound;
10
11 import java.util.ArrayList;
12 import java.util.Iterator;
13 import java.util.List;
14 import javax.ws.rs.Consumes;
15 import javax.ws.rs.DELETE;
16 import javax.ws.rs.GET;
17 import javax.ws.rs.POST;
18 import javax.ws.rs.PUT;
19 import javax.ws.rs.Path;
20 import javax.ws.rs.PathParam;
21 import javax.ws.rs.Produces;
22 import javax.ws.rs.QueryParam;
23 import javax.ws.rs.core.MediaType;
24 import javax.ws.rs.core.Response;
25
26 import org.codehaus.enunciate.jaxrs.ResponseCode;
27 import org.codehaus.enunciate.jaxrs.StatusCodes;
28 import org.opendaylight.controller.networkconfig.neutron.INeutronNetworkCRUD;
29 import org.opendaylight.controller.networkconfig.neutron.INeutronPortCRUD;
30 import org.opendaylight.controller.networkconfig.neutron.INeutronRouterAware;
31 import org.opendaylight.controller.networkconfig.neutron.INeutronRouterCRUD;
32 import org.opendaylight.controller.networkconfig.neutron.INeutronSubnetCRUD;
33 import org.opendaylight.controller.networkconfig.neutron.NeutronCRUDInterfaces;
34 import org.opendaylight.controller.networkconfig.neutron.NeutronNetwork;
35 import org.opendaylight.controller.networkconfig.neutron.NeutronPort;
36 import org.opendaylight.controller.networkconfig.neutron.NeutronRouter;
37 import org.opendaylight.controller.networkconfig.neutron.NeutronRouter_Interface;
38 import org.opendaylight.controller.networkconfig.neutron.NeutronSubnet;
39 import org.opendaylight.controller.northbound.commons.RestMessages;
40 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
41 import org.opendaylight.controller.sal.utils.ServiceHelper;
42
43
44 /**
45  * Open DOVE Northbound REST APIs.<br>
46  * This class provides REST APIs for managing the open DOVE
47  *
48  * <br>
49  * <br>
50  * Authentication scheme : <b>HTTP Basic</b><br>
51  * Authentication realm : <b>opendaylight</b><br>
52  * Transport : <b>HTTP and HTTPS</b><br>
53  * <br>
54  * HTTPS Authentication is disabled by default. Administrator can enable it in
55  * tomcat-server.xml after adding a proper keystore / SSL certificate from a
56  * trusted authority.<br>
57  * More info :
58  * http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
59  *
60  */
61
62 @Path("/routers")
63 public class NeutronRoutersNorthbound {
64
65     private NeutronRouter extractFields(NeutronRouter o, List<String> fields) {
66         return o.extractFields(fields);
67     }
68
69     /**
70      * Returns a list of all Routers */
71
72     @GET
73     @Produces({ MediaType.APPLICATION_JSON })
74     //@TypeHint(OpenStackRouters.class)
75     @StatusCodes({
76             @ResponseCode(code = 200, condition = "Operation successful"),
77             @ResponseCode(code = 401, condition = "Unauthorized"),
78             @ResponseCode(code = 501, condition = "Not Implemented") })
79     public Response listRouters(
80             // return fields
81             @QueryParam("fields") List<String> fields,
82             // note: openstack isn't clear about filtering on lists, so we aren't handling them
83             @QueryParam("id") String queryID,
84             @QueryParam("name") String queryName,
85             @QueryParam("admin_state_up") String queryAdminStateUp,
86             @QueryParam("status") String queryStatus,
87             @QueryParam("tenant_id") String queryTenantID,
88             @QueryParam("external_gateway_info") String queryExternalGatewayInfo,
89             // pagination
90             @QueryParam("limit") String limit,
91             @QueryParam("marker") String marker,
92             @QueryParam("page_reverse") String pageReverse
93             // sorting not supported
94             ) {
95         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
96         if (routerInterface == null) {
97             throw new ServiceUnavailableException("Router CRUD Interface "
98                     + RestMessages.SERVICEUNAVAILABLE.toString());
99         }
100         List<NeutronRouter> allRouters = routerInterface.getAllRouters();
101         List<NeutronRouter> ans = new ArrayList<NeutronRouter>();
102         Iterator<NeutronRouter> i = allRouters.iterator();
103         while (i.hasNext()) {
104             NeutronRouter oSS = i.next();
105             if ((queryID == null || queryID.equals(oSS.getID())) &&
106                     (queryName == null || queryName.equals(oSS.getName())) &&
107                     (queryAdminStateUp == null || queryAdminStateUp.equals(oSS.getAdminStateUp())) &&
108                     (queryStatus == null || queryStatus.equals(oSS.getStatus())) &&
109                     (queryExternalGatewayInfo == null || queryExternalGatewayInfo.equals(oSS.getExternalGatewayInfo())) &&
110                     (queryTenantID == null || queryTenantID.equals(oSS.getTenantID()))) {
111                 if (fields.size() > 0)
112                     ans.add(extractFields(oSS,fields));
113                 else
114                     ans.add(oSS);
115             }
116         }
117         //TODO: apply pagination to results
118         return Response.status(200).entity(
119                 new NeutronRouterRequest(ans)).build();
120     }
121
122     /**
123      * Returns a specific Router */
124
125     @Path("{routerUUID}")
126     @GET
127     @Produces({ MediaType.APPLICATION_JSON })
128     //@TypeHint(OpenStackRouters.class)
129     @StatusCodes({
130             @ResponseCode(code = 200, condition = "Operation successful"),
131             @ResponseCode(code = 401, condition = "Unauthorized"),
132             @ResponseCode(code = 403, condition = "Forbidden"),
133             @ResponseCode(code = 404, condition = "Not Found"),
134             @ResponseCode(code = 501, condition = "Not Implemented") })
135     public Response showRouter(
136             @PathParam("routerUUID") String routerUUID,
137             // return fields
138             @QueryParam("fields") List<String> fields) {
139         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
140         if (routerInterface == null) {
141             throw new ServiceUnavailableException("Router CRUD Interface "
142                     + RestMessages.SERVICEUNAVAILABLE.toString());
143         }
144         if (!routerInterface.routerExists(routerUUID))
145             return Response.status(404).build();
146         if (fields.size() > 0) {
147             NeutronRouter ans = routerInterface.getRouter(routerUUID);
148             return Response.status(200).entity(
149                     new NeutronRouterRequest(extractFields(ans, fields))).build();
150         } else
151             return Response.status(200).entity(
152                     new NeutronRouterRequest(routerInterface.getRouter(routerUUID))).build();
153     }
154
155     /**
156      * Creates new Routers */
157
158     @POST
159     @Produces({ MediaType.APPLICATION_JSON })
160     @Consumes({ MediaType.APPLICATION_JSON })
161     //@TypeHint(OpenStackRouters.class)
162     @StatusCodes({
163             @ResponseCode(code = 201, condition = "Created"),
164             @ResponseCode(code = 400, condition = "Bad Request"),
165             @ResponseCode(code = 401, condition = "Unauthorized"),
166             @ResponseCode(code = 501, condition = "Not Implemented") })
167     public Response createRouters(final NeutronRouterRequest input) {
168         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
169         if (routerInterface == null) {
170             throw new ServiceUnavailableException("Router CRUD Interface "
171                     + RestMessages.SERVICEUNAVAILABLE.toString());
172         }
173         INeutronNetworkCRUD networkInterface = NeutronCRUDInterfaces.getINeutronNetworkCRUD( this);
174         if (networkInterface == null) {
175             throw new ServiceUnavailableException("Network CRUD Interface "
176                     + RestMessages.SERVICEUNAVAILABLE.toString());
177         }
178         if (input.isSingleton()) {
179             NeutronRouter singleton = input.getSingleton();
180
181             /*
182              * verify that the router doesn't already exist (issue: is deeper inspection necessary?)
183              * if there is external gateway information provided, verify that the specified network
184              * exists and has been designated as "router:external"
185              */
186             if (routerInterface.routerExists(singleton.getID()))
187                 return Response.status(400).build();
188             if (singleton.getExternalGatewayInfo() != null) {
189                 String externNetworkPtr = singleton.getExternalGatewayInfo().getNetworkID();
190                 if (!networkInterface.networkExists(externNetworkPtr))
191                     return Response.status(400).build();
192                 NeutronNetwork externNetwork = networkInterface.getNetwork(externNetworkPtr);
193                 if (!externNetwork.isRouterExternal())
194                     return Response.status(400).build();
195             }
196             Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
197             if (instances != null) {
198                 for (Object instance : instances) {
199                     INeutronRouterAware service = (INeutronRouterAware) instance;
200                     int status = service.canCreateRouter(singleton);
201                     if (status < 200 || status > 299)
202                         return Response.status(status).build();
203                 }
204             }
205
206             /*
207              * add router to the cache
208              */
209             routerInterface.addRouter(singleton);
210             if (instances != null) {
211                 for (Object instance : instances) {
212                     INeutronRouterAware service = (INeutronRouterAware) instance;
213                     service.neutronRouterCreated(singleton);
214                 }
215             }
216         } else {
217
218             /*
219              * only singleton router creates supported
220              */
221             return Response.status(400).build();
222         }
223         return Response.status(201).entity(input).build();
224     }
225
226     /**
227      * Updates a Router */
228
229     @Path("{routerUUID}")
230     @PUT
231     @Produces({ MediaType.APPLICATION_JSON })
232     @Consumes({ MediaType.APPLICATION_JSON })
233     //@TypeHint(OpenStackRouters.class)
234     @StatusCodes({
235             @ResponseCode(code = 200, condition = "Operation successful"),
236             @ResponseCode(code = 400, condition = "Bad Request"),
237             @ResponseCode(code = 401, condition = "Unauthorized"),
238             @ResponseCode(code = 404, condition = "Not Found"),
239             @ResponseCode(code = 501, condition = "Not Implemented") })
240     public Response updateRouter(
241             @PathParam("routerUUID") String routerUUID,
242             NeutronRouterRequest input
243             ) {
244         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
245         if (routerInterface == null) {
246             throw new ServiceUnavailableException("Router CRUD Interface "
247                     + RestMessages.SERVICEUNAVAILABLE.toString());
248         }
249         INeutronNetworkCRUD networkInterface = NeutronCRUDInterfaces.getINeutronNetworkCRUD( this);
250         if (networkInterface == null) {
251             throw new ServiceUnavailableException("Network CRUD Interface "
252                     + RestMessages.SERVICEUNAVAILABLE.toString());
253         }
254
255         /*
256          * router has to exist and only a single delta can be supplied
257          */
258         if (!routerInterface.routerExists(routerUUID))
259             return Response.status(404).build();
260         if (!input.isSingleton())
261             return Response.status(400).build();
262         NeutronRouter singleton = input.getSingleton();
263         NeutronRouter original = routerInterface.getRouter(routerUUID);
264
265         /*
266          * attribute changes blocked by Neutron
267          */
268         if (singleton.getID() != null || singleton.getTenantID() != null ||
269                 singleton.getStatus() != null)
270             return Response.status(400).build();
271
272         Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
273         if (instances != null) {
274             for (Object instance : instances) {
275                 INeutronRouterAware service = (INeutronRouterAware) instance;
276                 int status = service.canUpdateRouter(singleton, original);
277                 if (status < 200 || status > 299)
278                     return Response.status(status).build();
279             }
280         }
281         /*
282          * if the external gateway info is being changed, verify that the new network
283          * exists and has been designated as an external network
284          */
285         if (singleton.getExternalGatewayInfo() != null) {
286             String externNetworkPtr = singleton.getExternalGatewayInfo().getNetworkID();
287             if (!networkInterface.networkExists(externNetworkPtr))
288                 return Response.status(400).build();
289             NeutronNetwork externNetwork = networkInterface.getNetwork(externNetworkPtr);
290             if (!externNetwork.isRouterExternal())
291                 return Response.status(400).build();
292         }
293
294         /*
295          * update the router entry and return the modified object
296          */
297         routerInterface.updateRouter(routerUUID, singleton);
298         NeutronRouter updatedRouter = routerInterface.getRouter(routerUUID);
299         if (instances != null) {
300             for (Object instance : instances) {
301                 INeutronRouterAware service = (INeutronRouterAware) instance;
302                 service.neutronRouterUpdated(updatedRouter);
303             }
304         }
305         return Response.status(200).entity(
306                 new NeutronRouterRequest(routerInterface.getRouter(routerUUID))).build();
307
308     }
309
310     /**
311      * Deletes a Router */
312
313     @Path("{routerUUID}")
314     @DELETE
315     @StatusCodes({
316             @ResponseCode(code = 204, condition = "No Content"),
317             @ResponseCode(code = 401, condition = "Unauthorized"),
318             @ResponseCode(code = 404, condition = "Not Found"),
319             @ResponseCode(code = 409, condition = "Conflict"),
320             @ResponseCode(code = 501, condition = "Not Implemented") })
321     public Response deleteRouter(
322             @PathParam("routerUUID") String routerUUID) {
323         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
324         if (routerInterface == null) {
325             throw new ServiceUnavailableException("Router CRUD Interface "
326                     + RestMessages.SERVICEUNAVAILABLE.toString());
327         }
328
329         /*
330          * verify that the router exists and is not in use before removing it
331          */
332         if (!routerInterface.routerExists(routerUUID))
333             return Response.status(404).build();
334         if (routerInterface.routerInUse(routerUUID))
335             return Response.status(409).build();
336         NeutronRouter singleton = routerInterface.getRouter(routerUUID);
337         Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
338         if (instances != null) {
339             for (Object instance : instances) {
340                 INeutronRouterAware service = (INeutronRouterAware) instance;
341                 int status = service.canDeleteRouter(singleton);
342                 if (status < 200 || status > 299)
343                     return Response.status(status).build();
344             }
345         }
346         routerInterface.removeRouter(routerUUID);
347         if (instances != null) {
348             for (Object instance : instances) {
349                 INeutronRouterAware service = (INeutronRouterAware) instance;
350                 service.neutronRouterDeleted(singleton);
351             }
352         }
353         return Response.status(204).build();
354     }
355
356     /**
357      * Adds an interface to a router */
358
359     @Path("{routerUUID}/add_router_interface")
360     @PUT
361     @Produces({ MediaType.APPLICATION_JSON })
362     @Consumes({ MediaType.APPLICATION_JSON })
363     //@TypeHint(OpenStackRouterInterfaces.class)
364     @StatusCodes({
365             @ResponseCode(code = 200, condition = "Operation successful"),
366             @ResponseCode(code = 400, condition = "Bad Request"),
367             @ResponseCode(code = 401, condition = "Unauthorized"),
368             @ResponseCode(code = 404, condition = "Not Found"),
369             @ResponseCode(code = 409, condition = "Conflict"),
370             @ResponseCode(code = 501, condition = "Not Implemented") })
371     public Response addRouterInterface(
372             @PathParam("routerUUID") String routerUUID,
373             NeutronRouter_Interface input
374             ) {
375         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
376         if (routerInterface == null) {
377             throw new ServiceUnavailableException("Router CRUD Interface "
378                     + RestMessages.SERVICEUNAVAILABLE.toString());
379         }
380         INeutronPortCRUD portInterface = NeutronCRUDInterfaces.getINeutronPortCRUD(this);
381         if (portInterface == null) {
382             throw new ServiceUnavailableException("Port CRUD Interface "
383                     + RestMessages.SERVICEUNAVAILABLE.toString());
384         }
385         INeutronSubnetCRUD subnetInterface = NeutronCRUDInterfaces.getINeutronSubnetCRUD(this);
386         if (subnetInterface == null) {
387             throw new ServiceUnavailableException("Subnet CRUD Interface "
388                     + RestMessages.SERVICEUNAVAILABLE.toString());
389         }
390
391         /*
392          *  While the Neutron specification says that the router has to exist and the input can only specify either a subnet id
393          *  or a port id, but not both, this code assumes that the plugin has filled everything in for us and so both must be present
394          */
395         if (!routerInterface.routerExists(routerUUID))
396             return Response.status(400).build();
397         NeutronRouter target = routerInterface.getRouter(routerUUID);
398         if (input.getSubnetUUID() == null ||
399                     input.getPortUUID() == null)
400                 return Response.status(400).build();
401
402         // check that the port is part of the subnet
403         NeutronSubnet targetSubnet = subnetInterface.getSubnet(input.getSubnetUUID());
404         if (targetSubnet == null)
405             return Response.status(400).build();
406         NeutronPort targetPort = portInterface.getPort(input.getPortUUID());
407         if (targetPort == null)
408             return Response.status(400).build();
409         if (!targetSubnet.getPortsInSubnet().contains(targetPort))
410             return Response.status(400).build();
411
412         if (targetPort.getFixedIPs().size() != 1)
413             return Response.status(400).build();
414         if (targetPort.getDeviceID() != null ||
415                 targetPort.getDeviceOwner() != null)
416             return Response.status(409).build();
417
418         Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
419         if (instances != null) {
420             for (Object instance : instances) {
421                 INeutronRouterAware service = (INeutronRouterAware) instance;
422                 service.canAttachInterface(target, input);
423             }
424         }
425
426         //mark the port device id and device owner fields
427         targetPort.setDeviceOwner("network:router_interface");
428         targetPort.setDeviceID(routerUUID);
429
430         target.addInterface(input.getPortUUID(), input);
431         if (instances != null) {
432             for (Object instance : instances) {
433                 INeutronRouterAware service = (INeutronRouterAware) instance;
434                 service.neutronRouterInterfaceAttached(target, input);
435             }
436         }
437
438         return Response.status(200).entity(input).build();
439     }
440
441     /**
442      * Removes an interface to a router */
443
444     @Path("{routerUUID}/remove_router_interface")
445     @PUT
446     @Produces({ MediaType.APPLICATION_JSON })
447     @Consumes({ MediaType.APPLICATION_JSON })
448     //@TypeHint(OpenStackRouterInterfaces.class)
449     @StatusCodes({
450             @ResponseCode(code = 200, condition = "Operation successful"),
451             @ResponseCode(code = 400, condition = "Bad Request"),
452             @ResponseCode(code = 401, condition = "Unauthorized"),
453             @ResponseCode(code = 404, condition = "Not Found"),
454             @ResponseCode(code = 409, condition = "Conflict"),
455             @ResponseCode(code = 501, condition = "Not Implemented") })
456     public Response removeRouterInterface(
457             @PathParam("routerUUID") String routerUUID,
458             NeutronRouter_Interface input
459             ) {
460         INeutronRouterCRUD routerInterface = NeutronCRUDInterfaces.getINeutronRouterCRUD(this);
461         if (routerInterface == null) {
462             throw new ServiceUnavailableException("Router CRUD Interface "
463                     + RestMessages.SERVICEUNAVAILABLE.toString());
464         }
465         INeutronPortCRUD portInterface = NeutronCRUDInterfaces.getINeutronPortCRUD(this);
466         if (portInterface == null) {
467             throw new ServiceUnavailableException("Port CRUD Interface "
468                     + RestMessages.SERVICEUNAVAILABLE.toString());
469         }
470         INeutronSubnetCRUD subnetInterface = NeutronCRUDInterfaces.getINeutronSubnetCRUD(this);
471         if (subnetInterface == null) {
472             throw new ServiceUnavailableException("Subnet CRUD Interface "
473                     + RestMessages.SERVICEUNAVAILABLE.toString());
474         }
475
476         // verify the router exists
477         if (!routerInterface.routerExists(routerUUID))
478             return Response.status(400).build();
479         NeutronRouter target = routerInterface.getRouter(routerUUID);
480
481         /*
482          * remove by subnet id.  Collect information about the impacted router for the response and
483          * remove the port corresponding to the gateway IP address of the subnet
484          */
485         if (input.getPortUUID() == null &&
486                 input.getSubnetUUID() != null) {
487             NeutronPort port = portInterface.getGatewayPort(input.getSubnetUUID());
488             if (port == null)
489                 return Response.status(404).build();
490             input.setPortUUID(port.getID());
491             input.setID(target.getID());
492             input.setTenantID(target.getTenantID());
493
494             Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
495             if (instances != null) {
496                 for (Object instance : instances) {
497                     INeutronRouterAware service = (INeutronRouterAware) instance;
498                     service.canDetachInterface(target, input);
499                 }
500             }
501
502             // reset the port ownership
503             port.setDeviceID(null);
504             port.setDeviceOwner(null);
505
506             target.removeInterface(input.getPortUUID());
507             if (instances != null) {
508                 for (Object instance : instances) {
509                     INeutronRouterAware service = (INeutronRouterAware) instance;
510                     service.neutronRouterInterfaceDetached(target, input);
511                 }
512             }
513             return Response.status(200).entity(input).build();
514         }
515
516         /*
517          * remove by port id. collect information about the impacted router for the response
518          * remove the interface and reset the port ownership
519          */
520         if (input.getPortUUID() != null &&
521                 input.getSubnetUUID() == null) {
522             NeutronRouter_Interface targetInterface = target.getInterfaces().get(input.getPortUUID());
523             input.setSubnetUUID(targetInterface.getSubnetUUID());
524             input.setID(target.getID());
525             input.setTenantID(target.getTenantID());
526             NeutronPort port = portInterface.getPort(input.getPortUUID());
527             port.setDeviceID(null);
528             port.setDeviceOwner(null);
529             target.removeInterface(input.getPortUUID());
530             Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
531             for (Object instance : instances) {
532                 INeutronRouterAware service = (INeutronRouterAware) instance;
533                 service.neutronRouterInterfaceDetached(target, input);
534             }
535             return Response.status(200).entity(input).build();
536         }
537
538         /*
539          * remove by both port and subnet ID.  Verify that the first fixed IP of the port is a valid
540          * IP address for the subnet, and then remove the interface, collecting information about the
541          * impacted router for the response and reset port ownership
542          */
543         if (input.getPortUUID() != null &&
544                 input.getSubnetUUID() != null) {
545             NeutronPort port = portInterface.getPort(input.getPortUUID());
546             NeutronSubnet subnet = subnetInterface.getSubnet(input.getSubnetUUID());
547             if (!subnet.isValidIP(port.getFixedIPs().get(0).getIpAddress()))
548                 return Response.status(409).build();
549             input.setID(target.getID());
550             input.setTenantID(target.getTenantID());
551             Object[] instances = ServiceHelper.getGlobalInstances(INeutronRouterAware.class, this, null);
552             if (instances != null) {
553                 for (Object instance : instances) {
554                     INeutronRouterAware service = (INeutronRouterAware) instance;
555                     service.canDetachInterface(target, input);
556                 }
557             }
558             port.setDeviceID(null);
559             port.setDeviceOwner(null);
560             target.removeInterface(input.getPortUUID());
561             for (Object instance : instances) {
562                 INeutronRouterAware service = (INeutronRouterAware) instance;
563                 service.neutronRouterInterfaceDetached(target, input);
564             }
565             return Response.status(200).entity(input).build();
566         }
567
568         // have to specify either a port ID or a subnet ID
569         return Response.status(400).build();
570     }
571 }