061d3fa4a2aedc357c99bbef70b41bac004acccb
[hc2vpp.git] /
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.honeycomb.v3po.translate.util.write;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.collect.HashMultimap;
23 import com.google.common.collect.Iterables;
24 import com.google.common.collect.Lists;
25 import com.google.common.collect.Multimap;
26 import io.fd.honeycomb.v3po.translate.util.RWUtils;
27 import io.fd.honeycomb.v3po.translate.write.WriteContext;
28 import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
29 import io.fd.honeycomb.v3po.translate.write.Writer;
30 import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import javax.annotation.Nonnull;
35 import javax.annotation.Nullable;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Simple writer registry able to perform and aggregated write (ROOT write) on top of all provided writers. Also able to
43  * delegate a specific write to one of the delegate writers.
44  *
45  * This could serve as a utility to hold & hide all available writers in upper layers.
46  */
47 public final class DelegatingWriterRegistry implements WriterRegistry {
48
49     private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class);
50
51     private final Map<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriters;
52
53     /**
54      * Create new {@link DelegatingWriterRegistry}
55      *
56      * @param rootWriters List of delegate writers
57      */
58     public DelegatingWriterRegistry(@Nonnull final List<Writer<? extends DataObject>> rootWriters) {
59         this.rootWriters = RWUtils.uniqueLinkedIndex(checkNotNull(rootWriters), RWUtils.MANAGER_CLASS_FUNCTION);
60     }
61
62     /**
63      * @throws UnsupportedOperationException This getter is not supported for writer registry since it does not manage a
64      *                                       specific node type
65      */
66     @Nonnull
67     @Override
68     public InstanceIdentifier<DataObject> getManagedDataObjectType() {
69         throw new UnsupportedOperationException("Root registry has no type");
70     }
71
72     @Override
73     public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
74                        @Nullable final DataObject dataBefore,
75                        @Nullable final DataObject dataAfter,
76                        @Nonnull final WriteContext ctx) throws WriteFailedException {
77         final InstanceIdentifier.PathArgument first = checkNotNull(
78                 Iterables.getFirst(id.getPathArguments(), null), "Empty id");
79         final Writer<? extends DataObject> writer = rootWriters.get(first.getType());
80         checkNotNull(writer,
81                 "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet());
82         writer.update(id, dataBefore, dataAfter, ctx);
83     }
84
85     @Override
86     public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
87                        @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter,
88                        @Nonnull final WriteContext ctx) throws WriteFailedException {
89
90         Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds = HashMultimap.create();
91         try {
92             checkAllWritersPresent(nodesBefore, rootIdToNestedIds);
93             checkAllWritersPresent(nodesAfter, rootIdToNestedIds);
94         } catch (IllegalArgumentException e) {
95             LOG.warn("Unable to process update", e);
96             throw e;
97         }
98
99         final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList();
100
101         for (Map.Entry<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriterEntry : rootWriters
102                 .entrySet()) {
103
104             final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType();
105             // FIXME !! this is not ideal, we are not handling nested updates in expected order
106             // Root writers are invoked in order they were registered, but nested updates are not, since they are
107             // iterated here.
108             //
109             for (InstanceIdentifier<?> specificInstanceIdentifier : rootIdToNestedIds.get(id)) {
110                 final DataObject dataBefore = nodesBefore.get(specificInstanceIdentifier);
111                 final DataObject dataAfter = nodesAfter.get(specificInstanceIdentifier);
112
113                 // No change to current writer
114                 if (dataBefore == null && dataAfter == null) {
115                     continue;
116                 }
117
118                 try {
119                     LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter);
120                     update(specificInstanceIdentifier, dataBefore, dataAfter, ctx);
121                     processedNodes.add(id);
122                 } catch (Exception e) {
123                     LOG.error("Error while processing data change of: {} (before={}, after={})", id, dataBefore, dataAfter, e);
124                     throw new BulkUpdateException(id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e);
125                 }
126             }
127         }
128     }
129
130     private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore,
131                                         final @Nonnull Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds) {
132         for (final InstanceIdentifier<?> changeId : nodesBefore.keySet()) {
133             final InstanceIdentifier.PathArgument first = Iterables.getFirst(changeId.getPathArguments(), null);
134             checkNotNull(first, "Empty identifier detected");
135             final InstanceIdentifier<? extends DataObject> rootId = InstanceIdentifier.create(first.getType());
136             checkArgument(rootWriters.keySet().contains(first.getType()),
137                     "Unable to handle change. Missing dedicated writer for: %s", first.getType());
138             rootIdToNestedIds.put(rootId, changeId);
139         }
140     }
141
142     private static final class ReverterImpl implements Reverter {
143         private final WriterRegistry delegatingWriterRegistry;
144         private final List<InstanceIdentifier<?>> processedNodes;
145         private final Map<InstanceIdentifier<?>, DataObject> nodesBefore;
146         private final Map<InstanceIdentifier<?>, DataObject> nodesAfter;
147         private final WriteContext ctx;
148
149         ReverterImpl(final WriterRegistry delegatingWriterRegistry,
150                             final List<InstanceIdentifier<?>> processedNodes,
151                             final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
152                             final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) {
153             this.delegatingWriterRegistry = delegatingWriterRegistry;
154             this.processedNodes = processedNodes;
155             this.nodesBefore = nodesBefore;
156             this.nodesAfter = nodesAfter;
157             this.ctx = ctx;
158         }
159
160         @Override
161         public void revert() throws RevertFailedException {
162             final LinkedList<InstanceIdentifier<?>> notReverted = new LinkedList<>(processedNodes);
163
164             while (notReverted.size() > 0) {
165                 final InstanceIdentifier<?> node = notReverted.peekLast();
166                 LOG.debug("ChangesProcessor.revertChanges() processing node={}", node);
167
168                 final DataObject dataBefore = nodesBefore.get(node);
169                 final DataObject dataAfter = nodesAfter.get(node);
170
171                 // revert a change by invoking writer with reordered arguments
172                 try {
173                     delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx);
174                     notReverted.removeLast(); // change was successfully reverted
175                 } catch (Exception e) {
176                     throw new RevertFailedException(notReverted, e);
177                 }
178
179             }
180         }
181     }
182 }