2 * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context;
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Throwables;
14 import com.google.common.cache.CacheBuilder;
15 import com.google.common.cache.CacheLoader;
16 import com.google.common.cache.LoadingCache;
17 import com.google.common.util.concurrent.UncheckedExecutionException;
18 import java.util.Iterator;
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;
51 * Creates RootNode from supplied CodecContextFactory and make operations with.
57 public final class SchemaRootCodecContext<D extends TreeNode> extends DataContainerCodecContext<D, SchemaContext> {
59 private final LoadingCache<Class<?>, DataContainerCodecContext<?, ?>> childrenByClass =
60 CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, DataContainerCodecContext<?, ?>>() {
62 public DataContainerCodecContext<?, ?> load(final Class<?> key) {
63 return createDataTreeChildContext(key);
67 private final LoadingCache<Class<?>, ContainerNodeCodecContext<?>> operationDataByClass =
68 CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, ContainerNodeCodecContext<?>>() {
70 public ContainerNodeCodecContext<?> load(final Class<?> key) {
71 return createOperationDataContext(key);
75 private final LoadingCache<Class<?>, NotificationCodecContext<?>> notificationsByClass =
76 CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, NotificationCodecContext<?>>() {
78 public NotificationCodecContext<?> load(final Class<?> key) {
79 return createNotificationDataContext(key);
83 private final LoadingCache<QName, DataContainerCodecContext<?, ?>> childrenByQName =
84 CacheBuilder.newBuilder().build(new CacheLoader<QName, DataContainerCodecContext<?, ?>>() {
85 @SuppressWarnings("unchecked")
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);
96 throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass());
100 private final LoadingCache<SchemaPath, OperationInputCodec<?>> operationDataByPath =
101 CacheBuilder.newBuilder().build(new CacheLoader<SchemaPath, OperationInputCodec<?>>() {
102 @SuppressWarnings({ "rawtypes", "unchecked" })
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
110 // data representation, so we hard-wire it to null.
111 return UnmappedOperationInputCodec.getInstance();
114 final Class cls = factory().getRuntimeContext().getClassForSchema(schema);
115 return getOperation(cls);
120 * Returns operation Input or Output Data container from operation definition.
123 * - SchemaContext in which lookup should be performed
125 * - Schema path of operation input/output data container
126 * @return operation schema or null, if operation is not present in schema context.
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);
140 return contSchemaNode;
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);
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()) {
158 return operation.getInput();
160 return operation.getOutput();
162 throw new IllegalArgumentException(
163 "Supplied qname " + qname + " does not represent operation input or output.");
167 private final LoadingCache<SchemaPath, NotificationCodecContext<?>> notificationsByPath =
168 CacheBuilder.newBuilder().build(new CacheLoader<SchemaPath, NotificationCodecContext<?>>() {
170 @SuppressWarnings({ "rawtypes", "unchecked" })
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);
179 private SchemaRootCodecContext(final DataContainerCodecPrototype<SchemaContext> dataPrototype) {
180 super(dataPrototype);
184 * Creates RootNode from supplied CodecContextFactory.
187 * - CodecContextFactory
188 * @return schema root node
190 public static SchemaRootCodecContext<?> create(final CodecContextFactory factory) {
191 final DataContainerCodecPrototype<SchemaContext> prototype = DataContainerCodecPrototype.rootPrototype(factory);
192 return new SchemaRootCodecContext<>(prototype);
195 @SuppressWarnings({ "unchecked", "rawtypes" })
198 public <C extends TreeNode> DataContainerCodecContext<C, ?> streamChild(@Nonnull final Class<C> childClass)
199 throws IllegalArgumentException {
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).
205 if (Notification.class.isAssignableFrom(childClass)) {
206 return (DataContainerCodecContext<C, ?>) getNotification((Class<? extends Notification>) childClass);
208 return (DataContainerCodecContext<C, ?>) getOrRethrow(childrenByClass, childClass);
212 public <E extends TreeNode> Optional<DataContainerCodecContext<E, ?>>
213 possibleStreamChild(@Nonnull final Class<E> childClass) {
214 throw new UnsupportedOperationException("Not supported");
219 public DataContainerCodecContext<?, ?> yangPathArgumentChild(final PathArgument arg) {
220 return getOrRethrow(childrenByQName, arg.getNodeType());
225 public D deserialize(@Nonnull final NormalizedNode<?, ?> normalizedNode) {
226 throw new UnsupportedOperationException("Could not create Binding data representation for root");
230 protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
231 throw new UnsupportedOperationException("Unable to deserialize root");
236 public TreeArgument<?> deserializePathArgument(@Nullable final YangInstanceIdentifier.PathArgument arg) {
237 Preconditions.checkArgument(arg == null);
243 public YangInstanceIdentifier.PathArgument serializePathArgument(@Nullable final TreeArgument<?> arg) {
244 Preconditions.checkArgument(arg == null);
249 * Get operation as binding object of binding class.
251 * @param operationInputOrOutput
253 * @return container node codec context of operation
255 public ContainerNodeCodecContext<?> getOperation(final Class<? extends Instantiable<?>> operationInputOrOutput) {
256 return getOrRethrow(operationDataByClass, operationInputOrOutput);
260 * Get operation input as binding object according to schema path of operation.
263 * - schema path of operation
264 * @return operation input codec of operation
266 public OperationInputCodec<?> getOperation(final SchemaPath operation) {
267 return getOrRethrow(operationDataByPath, operation);
271 * Get notification as binding object of binding class.
273 * @param notification
275 * @return notification codec context of notification
277 @SuppressWarnings("rawtypes")
278 public NotificationCodecContext<?> getNotification(final Class<? extends Notification> notification) {
279 return getOrRethrow(notificationsByClass, notification);
283 * Get notification as binding object according to schema path of notification.
285 * @param notification
286 * - schema path of notification
287 * @return notification codec context of notification
289 public NotificationCodecContext<?> getNotification(final SchemaPath notification) {
290 return getOrRethrow(notificationsByPath, notification);
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();
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);
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();
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();
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.
330 * FIXME: Rework this to have more precise logic regarding Binding Specification.
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;
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);
350 * FIXME: After Lithium cleanup of yang-model-api, use direct call on schema context to retrieve
351 * notification via index.
353 final NotificationDefinition schema =
354 SchemaContextUtil.getNotificationSchema(getSchema(), SchemaPath.create(true, qname));
355 Preconditions.checkArgument(schema != null, "Supplied %s is not valid notification", notificationType);
357 return new NotificationCodecContext<>(notificationType, schema, factory());
360 private static <K, V> V getOrRethrow(final LoadingCache<K, V> cache, final K key) {
362 return cache.getUnchecked(key);
363 } catch (final UncheckedExecutionException e) {
364 final Throwable cause = e.getCause();
366 Throwables.propagateIfPossible(cause);