HONEYCOMB-130: Separate v3po plugin from HC infra
[honeycomb.git] / infra / data-impl / src / main / java / io / fd / honeycomb / v3po / data / impl / ReadableDataTreeDelegator.java
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.data.impl;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.collect.Iterables.getOnlyElement;
21
22 import com.google.common.base.Function;
23 import com.google.common.base.Optional;
24 import com.google.common.base.Preconditions;
25 import com.google.common.collect.Collections2;
26 import com.google.common.collect.Multimap;
27 import com.google.common.util.concurrent.CheckedFuture;
28 import com.google.common.util.concurrent.Futures;
29 import io.fd.honeycomb.v3po.data.ReadableDataManager;
30 import io.fd.honeycomb.v3po.translate.MappingContext;
31 import io.fd.honeycomb.v3po.translate.ModificationCache;
32 import io.fd.honeycomb.v3po.translate.read.ReadContext;
33 import io.fd.honeycomb.v3po.translate.read.ReadFailedException;
34 import io.fd.honeycomb.v3po.translate.read.registry.ReaderRegistry;
35 import io.fd.honeycomb.v3po.translate.util.TransactionMappingContext;
36 import java.util.Collection;
37 import java.util.Map;
38 import javax.annotation.Nonnull;
39 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
40 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
41 import org.opendaylight.yangtools.yang.binding.DataObject;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
46 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
53 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
54 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 /**
61  * ReadableDataTree implementation for operational data.
62  */
63 public final class ReadableDataTreeDelegator implements ReadableDataManager {
64     private static final Logger LOG = LoggerFactory.getLogger(ReadableDataTreeDelegator.class);
65
66     private final BindingNormalizedNodeSerializer serializer;
67     private final ReaderRegistry readerRegistry;
68     private final SchemaContext globalContext;
69     private final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker;
70
71     /**
72      * Creates operational data tree instance.
73      * @param serializer     service for serialization between Java Binding Data representation and NormalizedNode
74      *                       representation.
75      * @param globalContext  service for obtaining top level context data from all yang modules.
76      * @param readerRegistry service responsible for translation between DataObjects and data provider.
77      * @param contextBroker BA broker for context data
78      */
79     public ReadableDataTreeDelegator(@Nonnull BindingNormalizedNodeSerializer serializer,
80                                      @Nonnull final SchemaContext globalContext,
81                                      @Nonnull final ReaderRegistry readerRegistry,
82                                      @Nonnull final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker) {
83         this.contextBroker = checkNotNull(contextBroker, "contextBroker should not be null");
84         this.globalContext = checkNotNull(globalContext, "globalContext should not be null");
85         this.serializer = checkNotNull(serializer, "serializer should not be null");
86         this.readerRegistry = checkNotNull(readerRegistry, "reader should not be null");
87     }
88
89     @Override
90     public CheckedFuture<Optional<NormalizedNode<?, ?>>,
91             org.opendaylight.controller.md.sal.common.api.data.ReadFailedException> read(
92             @Nonnull final YangInstanceIdentifier yangInstanceIdentifier) {
93
94         try(TransactionMappingContext mappingContext = new TransactionMappingContext(contextBroker.newReadWriteTransaction());
95             ReadContext ctx = new ReadContextImpl(mappingContext)) {
96
97             final Optional<NormalizedNode<?, ?>> value;
98             if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) {
99                 value = readRoot(ctx);
100             } else {
101                 value = readNode(yangInstanceIdentifier, ctx);
102             }
103
104             // Submit context mapping updates
105             final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
106                 ((TransactionMappingContext) ctx.getMappingContext()).submit();
107             // Blocking on context data update
108             contextUpdateResult.checkedGet();
109
110             return Futures.immediateCheckedFuture(value);
111
112         } catch (ReadFailedException e) {
113             return Futures.immediateFailedCheckedFuture(
114                 new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException(
115                     "Failed to read VPP data", e));
116         } catch (TransactionCommitFailedException e) {
117             // FIXME revert should probably occur when context is not written successfully
118             final String msg = "Error while updating mapping context data";
119             LOG.error(msg, e);
120             return Futures.immediateFailedCheckedFuture(
121                 new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException(msg, e)
122             );
123         }
124     }
125
126     private Optional<NormalizedNode<?, ?>> readNode(final YangInstanceIdentifier yangInstanceIdentifier,
127                                                     final ReadContext ctx) throws ReadFailedException {
128         LOG.debug("OperationalDataTree.readNode(), yangInstanceIdentifier={}", yangInstanceIdentifier);
129         final InstanceIdentifier<?> path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier);
130         checkNotNull(path, "Invalid instance identifier %s. Cannot create BA equivalent.", yangInstanceIdentifier);
131         LOG.debug("OperationalDataTree.readNode(), path={}", path);
132
133         final Optional<? extends DataObject> dataObject;
134
135         dataObject = readerRegistry.read(path, ctx);
136         if (dataObject.isPresent()) {
137             final NormalizedNode<?, ?> value = toNormalizedNodeFunction(path).apply(dataObject.get());
138             return Optional.<NormalizedNode<?, ?>>fromNullable(value);
139         } else {
140             return Optional.absent();
141         }
142     }
143
144     private Optional<NormalizedNode<?, ?>> readRoot(final ReadContext ctx) throws ReadFailedException {
145         LOG.debug("OperationalDataTree.readRoot()");
146
147         final DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> dataNodeBuilder =
148                 Builders.containerBuilder()
149                         .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME));
150
151         final Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> dataObjects =
152                 readerRegistry.readAll(ctx);
153
154         for (final InstanceIdentifier<? extends DataObject> instanceIdentifier : dataObjects.keySet()) {
155             final YangInstanceIdentifier rootElementId = serializer.toYangInstanceIdentifier(instanceIdentifier);
156             final NormalizedNode<?, ?> node =
157                     wrapDataObjects(rootElementId, instanceIdentifier, dataObjects.get(instanceIdentifier));
158             dataNodeBuilder.withChild((DataContainerChild<?, ?>) node);
159         }
160         return Optional.<NormalizedNode<?, ?>>of(dataNodeBuilder.build());
161     }
162
163     private NormalizedNode<?, ?> wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier,
164                                                  final InstanceIdentifier<? extends DataObject> instanceIdentifier,
165                                                  final Collection<? extends DataObject> dataObjects) {
166         final Collection<NormalizedNode<?, ?>> normalizedRootElements = Collections2
167                 .transform(dataObjects, toNormalizedNodeFunction(instanceIdentifier));
168
169         final DataSchemaNode schemaNode =
170                 globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType());
171         if (schemaNode instanceof ListSchemaNode) {
172             // In case of a list, wrap all the values in a Mixin parent node
173             final ListSchemaNode listSchema = (ListSchemaNode) schemaNode;
174             return wrapListIntoMixinNode(normalizedRootElements, listSchema);
175         } else {
176             Preconditions.checkState(dataObjects.size() == 1, "Singleton list was expected");
177             return getOnlyElement(normalizedRootElements);
178         }
179     }
180
181     private static DataContainerChild<?, ?> wrapListIntoMixinNode(
182             final Collection<NormalizedNode<?, ?>> normalizedRootElements, final ListSchemaNode listSchema) {
183         if (listSchema.getKeyDefinition().isEmpty()) {
184             final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> listBuilder =
185                     Builders.unkeyedListBuilder();
186             for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
187                 listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement);
188             }
189             return listBuilder.build();
190         } else {
191             final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> listBuilder =
192                     listSchema.isUserOrdered()
193                             ? Builders.orderedMapBuilder()
194                             : Builders.mapBuilder();
195
196             for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
197                 listBuilder.withChild((MapEntryNode) normalizedRootElement);
198             }
199             return listBuilder.build();
200         }
201     }
202
203     @SuppressWarnings("unchecked")
204     private Function<DataObject, NormalizedNode<?, ?>> toNormalizedNodeFunction(final InstanceIdentifier path) {
205         return dataObject -> {
206             LOG.trace("OperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject);
207             final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry =
208                     serializer.toNormalizedNode(path, dataObject);
209
210             LOG.trace("OperationalDataTree.toNormalizedNode(), normalizedNodeEntry={}", entry);
211             return entry.getValue();
212         };
213     }
214
215     private static final class ReadContextImpl implements ReadContext {
216
217         private final ModificationCache ctx = new ModificationCache();
218         private final MappingContext mappingContext;
219
220         private ReadContextImpl(final MappingContext mappingContext) {
221             this.mappingContext = mappingContext;
222         }
223
224         @Nonnull
225         @Override
226         public ModificationCache getModificationCache() {
227             return ctx;
228         }
229
230         @Nonnull
231         @Override
232         public MappingContext getMappingContext() {
233             return mappingContext;
234         }
235
236         @Override
237         public void close() {
238             // Make sure to clear the storage in case some customizer stored a reference to it to prevent memory leaks
239             ctx.close();
240         }
241     }
242 }