2e93a8e1849c7f5ab209742c54b547f5af405743
[transportpce.git] / pce / src / main / java / org / opendaylight / transportpce / pce / gnpy / GnpyServiceImpl.java
1 /*
2  * Copyright © 2019 Orange, Inc. and others.  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.transportpce.pce.gnpy;
10
11 import com.google.common.collect.HashBasedTable;
12 import com.google.common.collect.Table;
13 import java.math.BigDecimal;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.transportpce.common.ServiceRateConstant;
25 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
26 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
27 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
28 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
29 import org.opendaylight.transportpce.pce.gnpy.utils.AToZComparator;
30 import org.opendaylight.transportpce.pce.gnpy.utils.ZToAComparator;
31 import org.opendaylight.yang.gen.v1.gnpy.gnpy.network.topology.rev220221.topo.Elements;
32 import org.opendaylight.yang.gen.v1.gnpy.gnpy.network.topology.rev220221.topo.ElementsKey;
33 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.RouteIncludeEro;
34 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.TeHopType;
35 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.TePathDisjointness;
36 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.common.constraints_config.TeBandwidth;
37 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.common.constraints_config.TeBandwidthBuilder;
38 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.explicit.route.hop.Type;
39 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.explicit.route.hop.type.NumUnnumHopBuilder;
40 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.explicit.route.hop.type.num.unnum.hop.NumUnnumHop;
41 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.generic.path.constraints.PathConstraints;
42 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.generic.path.constraints.PathConstraintsBuilder;
43 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.gnpy.specific.parameters.EffectiveFreqSlot;
44 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.gnpy.specific.parameters.EffectiveFreqSlotBuilder;
45 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.path.route.objects.ExplicitRouteObjects;
46 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.path.route.objects.ExplicitRouteObjectsBuilder;
47 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.path.route.objects.explicit.route.objects.RouteObjectIncludeExclude;
48 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.path.route.objects.explicit.route.objects.RouteObjectIncludeExcludeBuilder;
49 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.path.route.objects.explicit.route.objects.RouteObjectIncludeExcludeKey;
50 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.service.PathRequest;
51 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.service.PathRequestBuilder;
52 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.service.PathRequestKey;
53 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.synchronization.info.Synchronization;
54 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.synchronization.info.SynchronizationBuilder;
55 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.synchronization.info.synchronization.Svec;
56 import org.opendaylight.yang.gen.v1.gnpy.path.rev220221.synchronization.info.synchronization.SvecBuilder;
57 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220118.PathComputationRequestInput;
58 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.optical.channel.types.rev211210.FrequencyTHz;
59 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.types.rev181019.ModulationFormat;
60 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705.path.description.AToZDirection;
61 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705.path.description.ZToADirection;
62 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705.path.description.atoz.direction.AToZ;
63 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705.path.description.ztoa.direction.ZToA;
64 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705.pce.resource.resource.Resource;
65 import org.opendaylight.yangtools.yang.common.Uint32;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70 /**
71  * Class to create the service corresponding to GNPy requirements.
72  *
73  * @author Ahmed Triki ( ahmed.triki@orange.com )
74  *
75  */
76
77 public class GnpyServiceImpl {
78     private static final Logger LOG = LoggerFactory.getLogger(GnpyServiceImpl.class);
79
80     private Map<PathRequestKey, PathRequest> pathRequest = new HashMap<>();
81     private List<Synchronization> synchronization = new ArrayList<>();
82     private Map<String, String> mapDisgNodeRefNode = new HashMap<>();
83     private Map<String, List<String>> mapLinkFiber = new HashMap<>();
84     private List<String> trxList = new ArrayList<>();
85     private Map<ElementsKey, Elements> elements = new HashMap<>();
86     private Map<RouteObjectIncludeExcludeKey, RouteObjectIncludeExclude> routeObjectIncludeExcludes = new HashMap<>();
87     private String currentNodeId = null;
88     private AToZComparator atoZComparator =  new AToZComparator();
89     private ZToAComparator ztoAComparator =  new ZToAComparator();
90     private static final Table<Uint32, BigDecimal, String> TRX_MODE_TABLE = initTrxModeTable();
91
92     private static Table<Uint32, BigDecimal, String> initTrxModeTable() {
93         Table<Uint32, BigDecimal, String> trxModeTable = HashBasedTable.create();
94         trxModeTable.put(ServiceRateConstant.RATE_100, GridConstant.SLOT_WIDTH_50, "100 Gbit/s, 27.95 Gbaud, DP-QPSK");
95         trxModeTable.put(ServiceRateConstant.RATE_200, GridConstant.SLOT_WIDTH_50, "200 Gbit/s, 31.57 Gbaud, DP-16QAM");
96         trxModeTable.put(ServiceRateConstant.RATE_200, GridConstant.SLOT_WIDTH_87_5, "200 Gbit/s, DP-QPSK");
97         trxModeTable.put(ServiceRateConstant.RATE_300, GridConstant.SLOT_WIDTH_87_5, "300 Gbit/s, DP-8QAM");
98         trxModeTable.put(ServiceRateConstant.RATE_400, GridConstant.SLOT_WIDTH_87_5, "400 Gbit/s, DP-16QAM");
99         return trxModeTable;
100     }
101
102     public static final Map<Uint32, BigDecimal> RATE_OUTPUTPOWER = Map.of(
103             ServiceRateConstant.RATE_100, GridConstant.OUTPUT_POWER_100GB_W,
104             ServiceRateConstant.RATE_400, GridConstant.OUTPUT_POWER_400GB_W);
105
106     /*
107      * Construct the GnpyServiceImpl
108      */
109     public GnpyServiceImpl(PathComputationRequestInput input, AToZDirection atoz, Uint32 requestId,
110                 GnpyTopoImpl gnpyTopo, PceConstraints pceHardConstraints) throws GnpyException {
111         this.elements = gnpyTopo.getElements();
112         this.mapDisgNodeRefNode = gnpyTopo.getMapDisgNodeRefNode();
113         this.mapLinkFiber = gnpyTopo.getMapLinkFiber();
114         this.trxList = gnpyTopo.getTrxList();
115         try {
116             this.pathRequest = extractPathRequest(input, atoz, requestId.toJava(), pceHardConstraints);
117             this.synchronization = extractSynchronization(requestId);
118         } catch (NullPointerException e) {
119             throw new GnpyException("In GnpyServiceImpl: one of the elements is null",e);
120         }
121     }
122
123     public GnpyServiceImpl(PathComputationRequestInput input, ZToADirection ztoa, Uint32 requestId,
124                 GnpyTopoImpl gnpyTopo, PceConstraints pceHardConstraints) throws GnpyException {
125         this.elements = gnpyTopo.getElements();
126         this.mapDisgNodeRefNode = gnpyTopo.getMapDisgNodeRefNode();
127         this.mapLinkFiber = gnpyTopo.getMapLinkFiber();
128         this.trxList = gnpyTopo.getTrxList();
129         try {
130             pathRequest = extractPathRequest(input, ztoa, requestId.toJava(), pceHardConstraints);
131             synchronization = extractSynchronization(requestId);
132         } catch (NullPointerException e) {
133             throw new GnpyException("In GnpyServiceImpl: one of the elements of service is null",e);
134         }
135     }
136
137     private Map<PathRequestKey, PathRequest> extractPathRequest(
138             PathComputationRequestInput input, AToZDirection atoz, Long requestId,
139             PceConstraints pceHardConstraints) throws GnpyException {
140
141         // Create the source and destination nodes
142         String sourceNode = input.getServiceAEnd().getNodeId();
143         String destNode = input.getServiceZEnd().getNodeId();
144         if (!trxList.contains(sourceNode) || !trxList.contains(destNode)) {
145             throw new GnpyException("In GnpyServiceImpl: source and destination should be transmitter nodes");
146         }
147
148         // Create explicitRouteObjects
149         List<AToZ> listAtoZ = new ArrayList<>(atoz.nonnullAToZ().values());
150         if (listAtoZ.isEmpty()) {
151             extractHardConstraints(pceHardConstraints);
152         } else {
153             Collections.sort(listAtoZ, atoZComparator);
154             extractRouteObjectIcludeAtoZ(listAtoZ);
155         }
156
157         ExplicitRouteObjects explicitRouteObjects = new ExplicitRouteObjectsBuilder()
158             .setRouteObjectIncludeExclude(routeObjectIncludeExcludes).build();
159         //Create Path Constraint
160         PathConstraints pathConstraints = createPathConstraints(atoz.getRate().toJava(),
161                 atoz.getModulationFormat(),
162                 atoz.getAToZMinFrequency(),
163                 atoz.getAToZMaxFrequency());
164
165         // Create the path request
166         Map<PathRequestKey, PathRequest> pathRequestMap = new HashMap<>();
167         PathRequest pathRequestEl = new PathRequestBuilder().setRequestId(requestId.toString())
168             .setSource(sourceNode)
169             .setDestination(destNode)
170             .setSrcTpId(sourceNode)
171             .setDstTpId(destNode)
172             .setBidirectional(false).setPathConstraints(pathConstraints).setPathConstraints(pathConstraints)
173             .setExplicitRouteObjects(explicitRouteObjects).build();
174         pathRequestMap.put(pathRequestEl.key(),pathRequestEl);
175         LOG.debug("In GnpyServiceImpl: path request AToZ is extracted");
176         return pathRequestMap;
177     }
178
179     private Map<PathRequestKey, PathRequest> extractPathRequest(
180             PathComputationRequestInput input, ZToADirection ztoa, Long requestId,
181             PceConstraints pceHardConstraints) throws GnpyException {
182         // Create the source and destination nodes
183         String sourceNode = input.getServiceZEnd().getNodeId();
184         String destNode = input.getServiceAEnd().getNodeId();
185         if (!trxList.contains(sourceNode) || !trxList.contains(destNode)) {
186             throw new GnpyException("In GnpyServiceImpl: source and destination should be transmitter nodes");
187         }
188         // Create explicitRouteObjects
189         @NonNull List<ZToA> listZtoA = new ArrayList<>(ztoa.nonnullZToA().values());
190         if (listZtoA.isEmpty()) {
191             extractHardConstraints(pceHardConstraints);
192         } else {
193             Collections.sort(listZtoA, ztoAComparator);
194             extractRouteObjectIcludeZtoA(listZtoA);
195         }
196
197         ExplicitRouteObjects explicitRouteObjects = new ExplicitRouteObjectsBuilder()
198             .setRouteObjectIncludeExclude(routeObjectIncludeExcludes).build();
199         //Create Path Constraint
200         PathConstraints pathConstraints = createPathConstraints(ztoa.getRate().toJava(),
201                 ztoa.getModulationFormat(),
202                 ztoa.getZToAMinFrequency(),
203                 ztoa.getZToAMaxFrequency());
204
205         //Create the path request
206         Map<PathRequestKey, PathRequest> pathRequestMap = new HashMap<>();
207         PathRequest pathRequestEl = new PathRequestBuilder().setRequestId(requestId.toString())
208             .setSource(sourceNode)
209             .setDestination(destNode)
210             .setSrcTpId(sourceNode)
211             .setDstTpId(destNode)
212             .setBidirectional(false).setPathConstraints(pathConstraints)
213             .setExplicitRouteObjects(explicitRouteObjects).build();
214         pathRequestMap.put(pathRequestEl.key(),pathRequestEl);
215         LOG.debug("In GnpyServiceImpl: path request ZToA is extracted is extracted");
216         return pathRequestMap;
217     }
218
219     //Extract RouteObjectIncludeExclude list in the case of pre-computed path A-to-Z
220     private void extractRouteObjectIcludeAtoZ(Collection<AToZ> listAtoZ) throws GnpyException {
221         Long index = 0L;
222         for (AToZ entry : listAtoZ) {
223             index = createResource(entry.getResource().getResource(),index);
224         }
225     }
226
227     //Extract RouteObjectIncludeExclude list in the case of pre-computed path Z-to-A
228     private void extractRouteObjectIcludeZtoA(@NonNull List<ZToA> listZtoA) throws GnpyException {
229         Long index = 0L;
230         for (ZToA entry : listZtoA) {
231             index = createResource(entry.getResource().getResource(),index);
232         }
233     }
234
235     //Create a new resource node or link
236     private Long createResource(@Nullable Resource resource, Long index) throws GnpyException {
237         Long idx = index;
238         if (resource
239             instanceof
240                 org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
241                     .pce.resource.resource.resource.Node) {
242             org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
243                 .pce.resource.resource.resource.Node node =
244                 (org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
245                     .pce.resource.resource.resource.Node) resource;
246             if (node.getNodeId() == null) {
247                 throw new GnpyException("In gnpyServiceImpl: nodeId is null");
248             }
249             idx = addNodeToRouteObject(this.mapDisgNodeRefNode.get(node.getNodeId()),idx);
250         }
251
252         if (resource
253             instanceof
254                 org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
255                     .pce.resource.resource.resource.Link) {
256             org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
257                 .pce.resource.resource.resource.Link link =
258                 (org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.pathdescription.rev210705
259                     .pce.resource.resource.resource.Link) resource;
260             idx = addLinkToRouteObject(link.getLinkId(),idx);
261         }
262         return idx;
263     }
264
265     //Create RouteObjectIncludeExclude list in the case of hard constraint
266     private void extractHardConstraints(PceConstraints pceHardConstraints) throws GnpyException {
267         List<String> listNodeToInclude = getListToInclude(pceHardConstraints);
268         if (!listNodeToInclude.isEmpty()) {
269             Long index = 0L;
270             for (int i = 0; i < listNodeToInclude.size(); i++) {
271                 String nodeId = listNodeToInclude.get(i);
272                 index = addNodeToRouteObject(nodeId, index);
273             }
274         }
275     }
276
277     //Create the list of nodes to include
278     private List<String> getListToInclude(PceConstraints pceHardConstraints) {
279         List<String> listNodeToInclude = new ArrayList<>();
280         if (pceHardConstraints != null) {
281             List<ResourcePair> listToInclude = pceHardConstraints.getListToInclude();
282             Iterator<ResourcePair> it = listToInclude.iterator();
283             while (it.hasNext()) {
284                 ResourcePair rs = it.next();
285                 if (rs.getType().name().equals("NODE")) {
286                     listNodeToInclude.add(rs.getName());
287                 }
288             }
289         }
290         return listNodeToInclude;
291     }
292
293     //Add a node to the route object
294     private Long addNodeToRouteObject(String nodeRef, Long index) throws GnpyException {
295         Long idx = index;
296         for (Elements element : this.elements.values()) {
297             if (element.getUid().equals(nodeRef)) {
298                 if ((this.currentNodeId == null) || (!this.currentNodeId.equals(nodeRef))) {
299                     this.currentNodeId = nodeRef;
300                     RouteObjectIncludeExclude routeObjectIncludeExclude = addRouteObjectIncludeExclude(nodeRef,
301                             Uint32.valueOf(1), idx);
302                     RouteObjectIncludeExcludeKey key = new RouteObjectIncludeExcludeKey(Uint32.valueOf(idx));
303                     routeObjectIncludeExcludes.put(key, routeObjectIncludeExclude);
304                     idx += 1;
305                 }
306                 return idx;
307             }
308         }
309         throw new GnpyException(String.format("In gnpyServiceImpl : NodeRef %s does not exist",nodeRef));
310     }
311
312     //Add a link to the route object
313     private Long addLinkToRouteObject(String linkId, Long index) throws GnpyException {
314         Long idx = index;
315         if (linkId == null) {
316             throw new GnpyException("In GnpyServiceImpl: the linkId is null");
317         }
318         //Only the ROADM-to-ROADM link are included in the route object
319         if (!mapLinkFiber.containsKey(linkId)) {
320             return idx;
321         }
322         List<String> listSubLink = this.mapLinkFiber.get(linkId);
323         if (listSubLink == null) {
324             throw new GnpyException(String.format("In gnpyServiceImpl addNodeRouteObject : no sublink in %s",linkId));
325         }
326         for (String subLink : listSubLink) {
327             RouteObjectIncludeExclude routeObjectIncludeExclude =
328                 addRouteObjectIncludeExclude(subLink, Uint32.valueOf(1),idx);
329             RouteObjectIncludeExcludeKey key = new RouteObjectIncludeExcludeKey(Uint32.valueOf(idx));
330             routeObjectIncludeExcludes.put(key, routeObjectIncludeExclude);
331             idx += 1;
332         }
333         return idx;
334     }
335
336     // Add routeObjectIncludeExclude
337     private RouteObjectIncludeExclude addRouteObjectIncludeExclude(String nodeId, Uint32 teTpValue, Long index) {
338         NumUnnumHop numUnnumHop = new org.opendaylight.yang.gen.v1.gnpy.path.rev220221.explicit.route.hop.type.num
339             .unnum.hop.NumUnnumHopBuilder()
340                 .setNodeId(nodeId)
341                 .setLinkTpId(teTpValue.toString())
342                 .setHopType(TeHopType.STRICT).build();
343         Type type1 = new NumUnnumHopBuilder().setNumUnnumHop(numUnnumHop).build();
344         // Create routeObjectIncludeExclude element
345         return new RouteObjectIncludeExcludeBuilder()
346             .setIndex(Uint32.valueOf(index)).setExplicitRouteUsage(RouteIncludeEro.class).setType(type1).build();
347     }
348
349     //Create the path constraints
350     private PathConstraints createPathConstraints(Long rate, String modulationFormat, FrequencyTHz minFrequency,
351             FrequencyTHz maxFrequency) {
352         BigDecimal spacing = GridConstant.SLOT_WIDTH_50;
353         int mvalue = GridConstant.NB_SLOTS_100G;
354         int nvalue = 0;
355         if (minFrequency != null && maxFrequency != null && modulationFormat != null) {
356             LOG.info("Creating path constraints for rate {}, modulationFormat {}, min freq {}, max freq {}", rate,
357                     modulationFormat, minFrequency, maxFrequency);
358             ModulationFormat mformat = ModulationFormat.DpQpsk;
359             Optional<ModulationFormat> optionalModulationFormat = ModulationFormat.forName(modulationFormat);
360             if (optionalModulationFormat.isPresent()) {
361                 mformat = optionalModulationFormat.get();
362             }
363             spacing = GridConstant.FREQUENCY_SLOT_WIDTH_TABLE.get(Uint32.valueOf(rate), mformat);
364             FrequencyTHz centralFrequency = GridUtils
365                     .getCentralFrequency(minFrequency.getValue(), maxFrequency.getValue());
366             int centralFrequencyBitSetIndex = GridUtils.getIndexFromFrequency(centralFrequency.getValue());
367             mvalue = (int) Math.ceil(spacing.doubleValue() / (GridConstant.GRANULARITY));
368             nvalue = GridUtils.getNFromFrequencyIndex(centralFrequencyBitSetIndex);
369         }
370         LOG.info("Creating path constraints for rate {}, mvalue {}, nvalue {}, spacing {}", rate,
371                 mvalue, nvalue, spacing);
372         EffectiveFreqSlot effectiveFreqSlot = new EffectiveFreqSlotBuilder()
373                 .setM(Uint32.valueOf(mvalue / 2)).setN(nvalue).build();
374
375         TeBandwidth teBandwidth = new TeBandwidthBuilder()
376                 .setPathBandwidth(BigDecimal.valueOf(rate * 1e9))
377                 .setTechnology("flexi-grid").setTrxType("OpenROADM MSA ver. 5.0")
378                 .setTrxMode(TRX_MODE_TABLE.get(Uint32.valueOf(rate), spacing))
379                 .setOutputPower(GridUtils.convertDbmW(GridConstant.OUTPUT_POWER_100GB_DBM
380                         + 10 * Math.log10(mvalue / (double)GridConstant.NB_SLOTS_100G)))
381                 .setEffectiveFreqSlot(Map.of(effectiveFreqSlot.key(), effectiveFreqSlot))
382                 .setSpacing(spacing.multiply(BigDecimal.valueOf(1e9))).build();
383         return new PathConstraintsBuilder().setTeBandwidth(teBandwidth).build();
384     }
385
386     //Create the synchronization
387     private List<Synchronization> extractSynchronization(Uint32 requestId) {
388         // Create RequestIdNumber
389         List<String> requestIdNumber = new ArrayList<>();
390         requestIdNumber.add(requestId.toString());
391         // Create a synchronization
392         Svec svec = new SvecBuilder().setRelaxable(true)
393             .setDisjointness(new TePathDisjointness(true, true, false))
394             .setRequestIdNumber(requestIdNumber).build();
395         List<Synchronization> synchro = new ArrayList<>();
396         Synchronization synchronization1 = new SynchronizationBuilder()
397                 .setSynchronizationId(Uint32.valueOf(0).toString())
398                 .setSvec(svec).build();
399         synchro.add(synchronization1);
400         return (synchro);
401     }
402
403     public Map<PathRequestKey, PathRequest> getPathRequest() {
404         return pathRequest;
405     }
406
407     public void setPathRequest(Map<PathRequestKey, PathRequest> pathRequest) {
408         this.pathRequest = pathRequest;
409     }
410
411     public List<Synchronization> getSynchronization() {
412         return synchronization;
413     }
414
415     public void setSynchronization(List<Synchronization> synchronization) {
416         this.synchronization = synchronization;
417     }
418
419 }