Mass-migrate to java.util.Optional
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / impl / context / SchemaRootCodecContext.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. 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 package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Throwables;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import com.google.common.util.concurrent.UncheckedExecutionException;
17 import java.util.Iterator;
18 import java.util.Optional;
19 import java.util.Set;
20 import javax.annotation.Nonnull;
21 import javax.annotation.Nullable;
22 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.OperationInputCodec;
23 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.UnmappedOperationInputCodec;
24 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.DataContainerCodecContext;
25 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.DataContainerCodecPrototype;
26 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
27 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
28 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
29 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
30 import org.opendaylight.mdsal.binding.javav2.spec.base.Notification;
31 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
32 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
41 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
43 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
45 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
46 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
47 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
48 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
49
50 /**
51  * Creates RootNode from supplied CodecContextFactory and make operations with.
52  *
53  * @param <D>
54  *            - tree node type
55  */
56 @Beta
57 public final class SchemaRootCodecContext<D extends TreeNode> extends DataContainerCodecContext<D, SchemaContext> {
58
59     private final LoadingCache<Class<?>, DataContainerCodecContext<?, ?>> childrenByClass =
60             CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, DataContainerCodecContext<?, ?>>() {
61                 @Override
62                 public DataContainerCodecContext<?, ?> load(final Class<?> key) {
63                     return createDataTreeChildContext(key);
64                 }
65             });
66
67     private final LoadingCache<Class<?>, ContainerNodeCodecContext<?>> operationDataByClass =
68             CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, ContainerNodeCodecContext<?>>() {
69                 @Override
70                 public ContainerNodeCodecContext<?> load(final Class<?> key) {
71                     return createOperationDataContext(key);
72                 }
73             });
74
75     private final LoadingCache<Class<?>, NotificationCodecContext<?>> notificationsByClass =
76             CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, NotificationCodecContext<?>>() {
77                 @Override
78                 public NotificationCodecContext<?> load(final Class<?> key) {
79                     return createNotificationDataContext(key);
80                 }
81             });
82
83     private final LoadingCache<QName, DataContainerCodecContext<?, ?>> childrenByQName =
84             CacheBuilder.newBuilder().build(new CacheLoader<QName, DataContainerCodecContext<?, ?>>() {
85                 @SuppressWarnings("unchecked")
86                 @Override
87                 public DataContainerCodecContext<?, ?> load(final QName qname) {
88                     final DataSchemaNode childSchema = getSchema().getDataChildByName(qname);
89                     childNonNull(childSchema, qname, "Argument %s is not valid child of %s", qname, getSchema());
90                     if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) {
91                         @SuppressWarnings("rawtypes")
92                         final Class childCls = factory().getRuntimeContext().getClassForSchema(childSchema);
93                         return streamChild(childCls);
94                     }
95
96                     throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass());
97                 }
98             });
99
100     private final LoadingCache<SchemaPath, OperationInputCodec<?>> operationDataByPath =
101             CacheBuilder.newBuilder().build(new CacheLoader<SchemaPath, OperationInputCodec<?>>() {
102                 @SuppressWarnings({ "rawtypes", "unchecked" })
103                 @Override
104                 public OperationInputCodec load(final SchemaPath key) {
105                     final ContainerSchemaNode schema = getOperationDataSchema(getSchema(), key);
106                     if (schema instanceof EffectiveStatement && ((EffectiveStatement) schema).getDeclared()
107                             .getStatementSource() != StatementSource.DECLARATION) {
108                         // This is an implicitly-defined input or output statement. We do not have a
109                         // corresponding
110                         // data representation, so we hard-wire it to null.
111                         return UnmappedOperationInputCodec.getInstance();
112                     }
113
114                     final Class cls = factory().getRuntimeContext().getClassForSchema(schema);
115                     return getOperation(cls);
116                 }
117             });
118
119     /**
120      * Returns operation Input or Output Data container from operation definition.
121      *
122      * @param schema
123      *            - SchemaContext in which lookup should be performed
124      * @param path
125      *            - Schema path of operation input/output data container
126      * @return operation schema or null, if operation is not present in schema context.
127      */
128     private ContainerSchemaNode getOperationDataSchema(final SchemaContext schema, final SchemaPath path) {
129         Preconditions.checkNotNull(schema, "Schema context must not be null.");
130         Preconditions.checkNotNull(path, "Schema path must not be null.");
131         final Iterator<QName> it = path.getPathFromRoot().iterator();
132         Preconditions.checkArgument(it.hasNext(), "Operation must have QName.");
133         final QName operationName = it.next();
134         Preconditions.checkArgument(it.hasNext(), "input or output must be part of path.");
135         final QName inOrOut = it.next();
136         ContainerSchemaNode contSchemaNode = null;
137         if ((contSchemaNode = getOperationDataSchema(schema.getOperations(), operationName, inOrOut)) == null) {
138             contSchemaNode = getOperationDataSchema(schema.getActions(), operationName, inOrOut);
139         }
140         return contSchemaNode;
141     }
142
143     private ContainerSchemaNode getOperationDataSchema(final Set<? extends OperationDefinition> operations,
144             final QName operationName, final QName inOrOut) {
145         for (final OperationDefinition potential : operations) {
146             if (operationName.equals(potential.getQName())) {
147                 return getOperationDataSchema(potential, inOrOut);
148             }
149         }
150         return null;
151     }
152
153     private ContainerSchemaNode getOperationDataSchema(final OperationDefinition operation, final QName qname) {
154         Preconditions.checkNotNull(operation, "Operation Schema must not be null.");
155         Preconditions.checkNotNull(qname, "QName must not be null.");
156         switch (qname.getLocalName()) {
157             case "input":
158                 return operation.getInput();
159             case "output":
160                 return operation.getOutput();
161             default:
162                 throw new IllegalArgumentException(
163                     "Supplied qname " + qname + " does not represent operation input or output.");
164         }
165     }
166
167     private final LoadingCache<SchemaPath, NotificationCodecContext<?>> notificationsByPath =
168             CacheBuilder.newBuilder().build(new CacheLoader<SchemaPath, NotificationCodecContext<?>>() {
169
170                 @SuppressWarnings({ "rawtypes", "unchecked" })
171                 @Override
172                 public NotificationCodecContext load(final SchemaPath key) throws Exception {
173                     final NotificationDefinition schema = SchemaContextUtil.getNotificationSchema(getSchema(), key);
174                     final Class clz = factory().getRuntimeContext().getClassForSchema(schema);
175                     return getNotification(clz);
176                 }
177             });
178
179     private SchemaRootCodecContext(final DataContainerCodecPrototype<SchemaContext> dataPrototype) {
180         super(dataPrototype);
181     }
182
183     /**
184      * Creates RootNode from supplied CodecContextFactory.
185      *
186      * @param factory
187      *            - CodecContextFactory
188      * @return schema root node
189      */
190     public static SchemaRootCodecContext<?> create(final CodecContextFactory factory) {
191         final DataContainerCodecPrototype<SchemaContext> prototype = DataContainerCodecPrototype.rootPrototype(factory);
192         return new SchemaRootCodecContext<>(prototype);
193     }
194
195     @SuppressWarnings({ "unchecked", "rawtypes" })
196     @Nonnull
197     @Override
198     public <C extends TreeNode> DataContainerCodecContext<C, ?> streamChild(@Nonnull final Class<C> childClass)
199             throws IllegalArgumentException {
200         /*
201          * FIXME: This is still not solved for operations TODO: Probably performance wise operations, Data and
202          * Notification loading cache should be merge for performance resons. Needs microbenchmark to
203          * determine which is faster (keeping them separate or in same cache).
204          */
205         if (Notification.class.isAssignableFrom(childClass)) {
206             return (DataContainerCodecContext<C, ?>) getNotification((Class<? extends Notification>) childClass);
207         }
208         return (DataContainerCodecContext<C, ?>) getOrRethrow(childrenByClass, childClass);
209     }
210
211     @Override
212     public <E extends TreeNode> Optional<DataContainerCodecContext<E, ?>>
213             possibleStreamChild(@Nonnull final Class<E> childClass) {
214         throw new UnsupportedOperationException("Not supported");
215     }
216
217     @Nonnull
218     @Override
219     public DataContainerCodecContext<?, ?> yangPathArgumentChild(final PathArgument arg) {
220         return getOrRethrow(childrenByQName, arg.getNodeType());
221     }
222
223     @Nonnull
224     @Override
225     public D deserialize(@Nonnull final NormalizedNode<?, ?> normalizedNode) {
226         throw new UnsupportedOperationException("Could not create Binding data representation for root");
227     }
228
229     @Override
230     protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
231         throw new UnsupportedOperationException("Unable to deserialize root");
232     }
233
234     @Nullable
235     @Override
236     public TreeArgument<?> deserializePathArgument(@Nullable final YangInstanceIdentifier.PathArgument arg) {
237         Preconditions.checkArgument(arg == null);
238         return null;
239     }
240
241     @Nullable
242     @Override
243     public YangInstanceIdentifier.PathArgument serializePathArgument(@Nullable final TreeArgument<?> arg) {
244         Preconditions.checkArgument(arg == null);
245         return null;
246     }
247
248     /**
249      * Get operation as binding object of binding class.
250      *
251      * @param operationInputOrOutput
252      *            - binding class
253      * @return container node codec context of operation
254      */
255     public ContainerNodeCodecContext<?> getOperation(final Class<? extends Instantiable<?>> operationInputOrOutput) {
256         return getOrRethrow(operationDataByClass, operationInputOrOutput);
257     }
258
259     /**
260      * Get operation input as binding object according to schema path of operation.
261      *
262      * @param operation
263      *            - schema path of operation
264      * @return operation input codec of operation
265      */
266     public OperationInputCodec<?> getOperation(final SchemaPath operation) {
267         return getOrRethrow(operationDataByPath, operation);
268     }
269
270     /**
271      * Get notification as binding object of binding class.
272      *
273      * @param notification
274      *            - binding class
275      * @return notification codec context of notification
276      */
277     @SuppressWarnings("rawtypes")
278     public NotificationCodecContext<?> getNotification(final Class<? extends Notification> notification) {
279         return getOrRethrow(notificationsByClass, notification);
280     }
281
282     /**
283      * Get notification as binding object according to schema path of notification.
284      *
285      * @param notification
286      *            - schema path of notification
287      * @return notification codec context of notification
288      */
289     public NotificationCodecContext<?> getNotification(final SchemaPath notification) {
290         return getOrRethrow(notificationsByPath, notification);
291     }
292
293
294
295     private DataContainerCodecContext<?, ?> createDataTreeChildContext(final Class<?> key) {
296         final QName qname = BindingReflections.findQName(key);
297         final DataSchemaNode childSchema =
298                 childNonNull(getSchema().getDataChildByName(qname), key, "%s is not top-level item.", key);
299         return DataContainerCodecPrototype.from(key, childSchema, factory()).get();
300     }
301
302     private ContainerNodeCodecContext<?> createOperationDataContext(final Class<?> key) {
303         Preconditions.checkArgument(Instantiable.class.isAssignableFrom(key));
304         final QName qname = BindingReflections.findQName(key);
305         final QNameModule module = qname.getModule();
306         OperationDefinition operation = null;
307         final SchemaContext schemaContext = getSchema();
308         if ((operation = findPotentialOperation(schemaContext.getOperations(), module, key, qname)) == null) {
309             operation = findPotentialOperation(schemaContext.getActions(), module, key, qname);
310         }
311         Preconditions.checkArgument(operation != null,
312             "Supplied class %s is not valid operation class.", key);
313         final ContainerSchemaNode schema = getOperationDataSchema(operation, qname);// SchemaNodeUtils
314         // .getRpcDataSchema(operation, qname);
315         Preconditions.checkArgument(schema != null, "Schema for %s does not define input / output.",
316                 operation.getQName());
317         return (ContainerNodeCodecContext<?>) DataContainerCodecPrototype.from(key, schema, factory()).get();
318     }
319
320     private OperationDefinition findPotentialOperation(final Set<? extends OperationDefinition> set,
321             final QNameModule module, final Class<?> key, final QName qname) {
322         OperationDefinition operation = null;
323         for (final OperationDefinition potential : getSchema().getOperations()) {
324             final QName potentialQName = potential.getQName();
325             /*
326              * Check if operation and class represents data from same module and then checks if operation
327              * local name produces same class name as class name appended with Input/Output based on QName
328              * associated with bidning class.
329              *
330              * FIXME: Rework this to have more precise logic regarding Binding Specification.
331              */
332             final String moduleClassName = JavaIdentifierNormalizer
333                     .normalizeSpecificIdentifier(potentialQName.getLocalName(), JavaIdentifier.CLASS);
334             final String keyClassName =
335                     JavaIdentifierNormalizer.normalizeSpecificIdentifier(qname.getLocalName(), JavaIdentifier.CLASS);
336             if (module.equals(potentialQName.getModule())
337                     && key.getSimpleName().equals(moduleClassName + keyClassName)) {
338                 operation = potential;
339                 break;
340             }
341         }
342         return operation;
343     }
344
345     private NotificationCodecContext<?> createNotificationDataContext(final Class<?> notificationType) {
346         Preconditions.checkArgument(Notification.class.isAssignableFrom(notificationType));
347         Preconditions.checkArgument(notificationType.isInterface(), "Supplied class must be interface.");
348         final QName qname = BindingReflections.findQName(notificationType);
349         /**
350          * FIXME: After Lithium cleanup of yang-model-api, use direct call on schema context to retrieve
351          * notification via index.
352          */
353         final NotificationDefinition schema =
354                 SchemaContextUtil.getNotificationSchema(getSchema(), SchemaPath.create(true, qname));
355         Preconditions.checkArgument(schema != null, "Supplied %s is not valid notification", notificationType);
356
357         return new NotificationCodecContext<>(notificationType, schema, factory());
358     }
359
360     private static <K, V> V getOrRethrow(final LoadingCache<K, V> cache, final K key) {
361         try {
362             return cache.getUnchecked(key);
363         } catch (final UncheckedExecutionException e) {
364             final Throwable cause = e.getCause();
365             if (cause != null) {
366                 Throwables.propagateIfPossible(cause);
367             }
368             throw e;
369         }
370     }
371 }