X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FSchemaRootCodecContext.java;h=8b80d4152ba5e6b6f7f51a5bef96aab6d44cc41a;hb=11408d627adca7eb71ac956c3ad01f75b6b91596;hp=e00999b7265a928938bc2e99b854d303eeb1edbf;hpb=72ed8ba8aa471801ee06384a359de5aba59b6d05;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/SchemaRootCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/SchemaRootCodecContext.java index e00999b726..8b80d4152b 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/SchemaRootCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/SchemaRootCodecContext.java @@ -10,6 +10,7 @@ package org.opendaylight.mdsal.binding.dom.codec.impl; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; import com.google.common.base.Throwables; import com.google.common.base.Verify; @@ -21,6 +22,20 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Optional; +import java.util.function.BiFunction; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException; +import org.opendaylight.mdsal.binding.model.api.JavaTypeName; +import org.opendaylight.mdsal.binding.runtime.api.ActionRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext; +import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes; +import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.ContainerLikeRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.DataRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.NotificationRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.RuntimeType; import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; import org.opendaylight.yangtools.util.ClassLoaderUtils; @@ -38,106 +53,159 @@ import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.model.api.ActionDefinition; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ContainerLike; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; -import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; -import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; -final class SchemaRootCodecContext extends DataContainerCodecContext { +final class SchemaRootCodecContext extends DataContainerCodecContext { - private final LoadingCache, DataContainerCodecContext> childrenByClass = CacheBuilder.newBuilder() - .build(new CacheLoader, DataContainerCodecContext>() { - @Override - public DataContainerCodecContext load(final Class key) { - return createDataTreeChildContext(key); - } - }); + private final LoadingCache, DataContainerCodecContext> childrenByClass = + CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public DataContainerCodecContext load(final Class key) { + return createDataTreeChildContext(key); + } + }); - private final LoadingCache>, ActionCodecContext> actionsByClass = CacheBuilder - .newBuilder().build(new CacheLoader>, ActionCodecContext>() { - @Override - public ActionCodecContext load(final Class> key) { - return createActionContext(key); - } - }); + private final LoadingCache>, ActionCodecContext> actionsByClass = + CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public ActionCodecContext load(final Class> key) { + return createActionContext(key); + } + }); - private final LoadingCache, ContainerNodeCodecContext> rpcDataByClass = CacheBuilder.newBuilder().build( - new CacheLoader, ContainerNodeCodecContext>() { - @Override - public ContainerNodeCodecContext load(final Class key) { - return createRpcDataContext(key); - } - }); + private final LoadingCache, ChoiceNodeCodecContext> choicesByClass = + CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public ChoiceNodeCodecContext load(final Class key) { + return createChoiceDataContext(key); + } + }); private final LoadingCache, NotificationCodecContext> notificationsByClass = CacheBuilder.newBuilder() - .build(new CacheLoader, NotificationCodecContext>() { - @Override - public NotificationCodecContext load(final Class key) { - return createNotificationDataContext(key); - } - }); + .build(new CacheLoader, NotificationCodecContext>() { + @Override + public NotificationCodecContext load(final Class key) { + checkArgument(key.isInterface(), "Supplied class must be interface."); + + // TODO: we should be able to work with bindingChild() instead of schemaTreeChild() here + final QName qname = BindingReflections.findQName(key); + final RuntimeType child = getType().schemaTreeChild(qname); + checkArgument(child instanceof NotificationRuntimeType, "Supplied %s is not valid notification", + key); + return new NotificationCodecContext<>(key, (NotificationRuntimeType) child, factory()); + } + }); - private final LoadingCache, ChoiceNodeCodecContext> choicesByClass = - CacheBuilder.newBuilder().build(new CacheLoader, ChoiceNodeCodecContext>() { - @Override - public ChoiceNodeCodecContext load(final Class key) { - return createChoiceDataContext(key); + private final LoadingCache, ContainerNodeCodecContext> rpcDataByClass = CacheBuilder.newBuilder() + .build(new CacheLoader, ContainerNodeCodecContext>() { + @Override + public ContainerNodeCodecContext load(final Class key) { + final BiFunction>> lookup; + if (RpcInput.class.isAssignableFrom(key)) { + lookup = BindingRuntimeTypes::findRpcInput; + } else if (RpcOutput.class.isAssignableFrom(key)) { + lookup = BindingRuntimeTypes::findRpcOutput; + } else { + throw new IllegalArgumentException(key + " does not represent an RPC container"); } - }); - - private final LoadingCache> childrenByQName = CacheBuilder.newBuilder().build( - new CacheLoader>() { - @Override - public DataContainerCodecContext load(final QName qname) { - final DataSchemaNode childSchema = getSchema().getDataChildByName(qname); - childNonNull(childSchema, qname,"Argument %s is not valid child of %s", qname,getSchema()); - if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) { - @SuppressWarnings("unchecked") - final Class childCls = (Class) - factory().getRuntimeContext().getClassForSchema(childSchema); - return streamChild(childCls); + + final CodecContextFactory factory = factory(); + final BindingRuntimeContext context = factory.getRuntimeContext(); + + final QName qname = BindingReflections.findQName(key); + final QNameModule qnameModule = qname.getModule(); + final Module module = context.getEffectiveModelContext().findModule(qnameModule) + .orElseThrow(() -> new IllegalArgumentException("Failed to find module for " + qnameModule)); + final String className = BindingMapping.getClassName(qname); + + for (final RpcDefinition potential : module.getRpcs()) { + final QName potentialQName = potential.getQName(); + /* + * Check if rpc and class represents data from same module and then checks if rpc local name + * produces same class name as class name appended with Input/Output based on QName associated + * with binding class. + * + * FIXME: Rework this to have more precise logic regarding Binding Specification. + */ + if (key.getSimpleName().equals(BindingMapping.getClassName(potentialQName) + className)) { + final ContainerLike schema = getRpcDataSchema(potential, qname); + checkArgument(schema != null, "Schema for %s does not define input / output.", potentialQName); + + final ContainerLikeRuntimeType type = lookup.apply(context.getTypes(), potentialQName) + .orElseThrow(() -> new IllegalArgumentException("Cannot find runtime type for " + key)); + + return (ContainerNodeCodecContext) DataContainerCodecPrototype.from(key, + (ContainerLikeRuntimeType) type, factory).get(); } + } - throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass()); + throw new IllegalArgumentException("Supplied class " + key + " is not valid RPC class."); + } + }); + + private final LoadingCache> childrenByQName = + CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public DataContainerCodecContext load(final QName qname) throws ClassNotFoundException { + final var type = getType(); + final var child = childNonNull(type.schemaTreeChild(qname), qname, + "Argument %s is not valid child of %s", qname, type); + if (!(child instanceof DataRuntimeType)) { + throw IncorrectNestingException.create("Argument %s is not valid data tree child of %s", qname, + type); } - }); - private final LoadingCache> rpcDataByPath = CacheBuilder.newBuilder().build( - new CacheLoader>() { + // TODO: improve this check? + final var childSchema = child.statement(); + if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) { + return streamChild(factory().getRuntimeContext().loadClass(child.javaType())); + } + + throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass()); + } + }); + + private final LoadingCache> rpcDataByPath = + CacheBuilder.newBuilder().build(new CacheLoader<>() { @Override public RpcInputCodec load(final Absolute key) { - final ContainerSchemaNode schema = SchemaContextUtil.getRpcDataSchema(getSchema(), key.asSchemaPath()); - @SuppressWarnings("unchecked") - final Class cls = (Class) - factory().getRuntimeContext().getClassForSchema(schema); - return getRpc(cls); + final var rpcName = key.firstNodeIdentifier(); + final var context = factory().getRuntimeContext(); + + final Class container; + switch (key.lastNodeIdentifier().getLocalName()) { + case "input": + container = context.getRpcInput(rpcName); + break; + case "output": + container = context.getRpcOutput(rpcName); + break; + default: + throw new IllegalArgumentException("Unhandled path " + key); + } + + return getRpc(container); } }); - private final LoadingCache> notificationsByPath = CacheBuilder.newBuilder() - .build(new CacheLoader>() { - @Override - public NotificationCodecContext load(final Absolute key) { - final NotificationDefinition schema = SchemaContextUtil.getNotificationSchema(getSchema(), - // FIXME: do not convert here! - key.asSchemaPath()); - @SuppressWarnings("unchecked") - final Class clz = (Class) - factory().getRuntimeContext().getClassForSchema(schema); - return getNotification(clz); - } - }); + private final LoadingCache> notificationsByPath = + CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public NotificationCodecContext load(final Absolute key) { + final Class cls = factory().getRuntimeContext().getClassForSchema(key); + checkArgument(Notification.class.isAssignableFrom(cls), "Path %s does not represent a notification", + key); + return getNotificationImpl(cls); + } + }); - private SchemaRootCodecContext(final DataContainerCodecPrototype dataPrototype) { + private SchemaRootCodecContext(final DataContainerCodecPrototype dataPrototype) { super(dataPrototype); } @@ -149,23 +217,18 @@ final class SchemaRootCodecContext extends DataContainerCo * @return A new root node */ static SchemaRootCodecContext create(final CodecContextFactory factory) { - final DataContainerCodecPrototype prototype = DataContainerCodecPrototype.rootPrototype(factory); - return new SchemaRootCodecContext<>(prototype); + return new SchemaRootCodecContext<>(DataContainerCodecPrototype.rootPrototype(factory)); } + @Override + public WithStatus getSchema() { + return getType().getEffectiveModelContext(); + } @SuppressWarnings("unchecked") @Override public DataContainerCodecContext streamChild(final Class childClass) { - /* FIXME: This is still not solved for RPCs - * TODO: Probably performance wise RPC, Data and Notification loading cache - * should be merge for performance resons. Needs microbenchmark to - * determine which is faster (keeping them separate or in same cache). - */ - if (Notification.class.isAssignableFrom(childClass)) { - return (DataContainerCodecContext) getNotification((Class)childClass); - } - return (DataContainerCodecContext) getOrRethrow(childrenByClass,childClass); + return (DataContainerCodecContext) getOrRethrow(childrenByClass, childClass); } @Override @@ -180,7 +243,7 @@ final class SchemaRootCodecContext extends DataContainerCo } @Override - public D deserialize(final NormalizedNode normalizedNode) { + public D deserialize(final NormalizedNode normalizedNode) { throw new UnsupportedOperationException("Could not create Binding data representation for root"); } @@ -188,14 +251,18 @@ final class SchemaRootCodecContext extends DataContainerCo return getOrRethrow(actionsByClass, action); } - NotificationCodecContext getNotification(final Class notification) { - return getOrRethrow(notificationsByClass, notification); - } - NotificationCodecContext getNotification(final Absolute notification) { return getOrRethrow(notificationsByPath, notification); } + NotificationCodecContext getNotification(final Class> notification) { + return getNotificationImpl(notification); + } + + private NotificationCodecContext getNotificationImpl(final Class notification) { + return getOrRethrow(notificationsByClass, notification); + } + ContainerNodeCodecContext getRpc(final Class rpcInputOrOutput) { return getOrRethrow(rpcDataByClass, rpcInputOrOutput); } @@ -204,11 +271,13 @@ final class SchemaRootCodecContext extends DataContainerCo return getOrRethrow(rpcDataByPath, containerPath); } - DataContainerCodecContext createDataTreeChildContext(final Class key) { - final QName qname = BindingReflections.findQName(key); - final DataSchemaNode childSchema = childNonNull(getSchema().getDataChildByName(qname), key, + DataContainerCodecContext createDataTreeChildContext(final Class key) { + final RuntimeType childSchema = childNonNull(getType().bindingChild(JavaTypeName.create(key)), key, "%s is not top-level item.", key); - return DataContainerCodecPrototype.from(key, childSchema, factory()).get(); + if (childSchema instanceof CompositeRuntimeType && childSchema instanceof DataRuntimeType) { + return DataContainerCodecPrototype.from(key, (CompositeRuntimeType) childSchema, factory()).get(); + } + throw IncorrectNestingException.create("%s is not a valid data tree child of %s", key, this); } ActionCodecContext createActionContext(final Class> action) { @@ -228,12 +297,12 @@ final class SchemaRootCodecContext extends DataContainerCo final ParameterizedType paramType = optParamType.get(); final Type[] args = paramType.getActualTypeArguments(); checkArgument(args.length == expectedArgsLength, "Unexpected (%s) Action generatic arguments", args.length); - final ActionDefinition schema = factory().getRuntimeContext().getActionDefinition(action); + final ActionRuntimeType schema = factory().getRuntimeContext().getActionDefinition(action); return new ActionCodecContext( - DataContainerCodecPrototype.from(asClass(args[inputOffset], RpcInput.class), schema.getInput(), - factory()).get(), - DataContainerCodecPrototype.from(asClass(args[outputOffset], RpcOutput.class), schema.getOutput(), - factory()).get()); + DataContainerCodecPrototype.from(asClass(args[inputOffset], RpcInput.class), schema.input(), + factory()).get(), + DataContainerCodecPrototype.from(asClass(args[outputOffset], RpcOutput.class), schema.output(), + factory()).get()); } private static Class asClass(final Type type, final Class target) { @@ -241,61 +310,41 @@ final class SchemaRootCodecContext extends DataContainerCo return ((Class) type).asSubclass(target); } - ContainerNodeCodecContext createRpcDataContext(final Class key) { - checkArgument(DataContainer.class.isAssignableFrom(key)); - final QName qname = BindingReflections.findQName(key); - final QNameModule qnameModule = qname.getModule(); - final Module module = getSchema().findModule(qnameModule) - .orElseThrow(() -> new IllegalArgumentException("Failed to find module for " + qnameModule)); - final String className = BindingMapping.getClassName(qname); - - for (final RpcDefinition potential : module.getRpcs()) { - final QName potentialQName = potential.getQName(); - /* - * Check if rpc and class represents data from same module and then checks if rpc local name produces same - * class name as class name appended with Input/Output based on QName associated with binding class. - * - * FIXME: Rework this to have more precise logic regarding Binding Specification. - */ - if (key.getSimpleName().equals(BindingMapping.getClassName(potentialQName) + className)) { - final ContainerSchemaNode schema = SchemaNodeUtils.getRpcDataSchema(potential, qname); - checkArgument(schema != null, "Schema for %s does not define input / output.", potential.getQName()); - return (ContainerNodeCodecContext) DataContainerCodecPrototype.from(key, schema, factory()).get(); - } + /** + * Returns RPC input or output schema based on supplied QName. + * + * @param rpc RPC Definition + * @param qname input or output QName with namespace same as RPC + * @return input or output schema. Returns null if RPC does not have input/output specified. + */ + private static @Nullable ContainerLike getRpcDataSchema(final @NonNull RpcDefinition rpc, + final @NonNull QName qname) { + requireNonNull(rpc, "Rpc Schema must not be null"); + switch (requireNonNull(qname, "QName must not be null").getLocalName()) { + case "input": + return rpc.getInput(); + case "output": + return rpc.getOutput(); + default: + throw new IllegalArgumentException("Supplied qname " + qname + + " does not represent rpc input or output."); } - - throw new IllegalArgumentException("Supplied class " + key + " is not valid RPC class."); - } - - NotificationCodecContext createNotificationDataContext(final Class notificationType) { - checkArgument(Notification.class.isAssignableFrom(notificationType)); - checkArgument(notificationType.isInterface(), "Supplied class must be interface."); - final QName qname = BindingReflections.findQName(notificationType); - /** - * FIXME: After Lithium cleanup of yang-model-api, use direct call on schema context - * to retrieve notification via index. - */ - final NotificationDefinition schema = SchemaContextUtil.getNotificationSchema(getSchema(), - SchemaPath.create(true, qname)); - checkArgument(schema != null, "Supplied %s is not valid notification", notificationType); - - return new NotificationCodecContext<>(notificationType, schema, factory()); } ChoiceNodeCodecContext createChoiceDataContext(final Class caseType) { final Class choiceClass = findCaseChoice(caseType); checkArgument(choiceClass != null, "Class %s is not a valid case representation", caseType); - final DataSchemaNode schema = factory().getRuntimeContext().getSchemaDefinition(choiceClass); - checkArgument(schema instanceof ChoiceSchemaNode, "Class %s does not refer to a choice", caseType); + final CompositeRuntimeType schema = factory().getRuntimeContext().getSchemaDefinition(choiceClass); + checkArgument(schema instanceof ChoiceRuntimeType, "Class %s does not refer to a choice", caseType); - final DataContainerCodecContext choice = DataContainerCodecPrototype.from(choiceClass, - (ChoiceSchemaNode)schema, factory()).get(); + final DataContainerCodecContext choice = DataContainerCodecPrototype.from(choiceClass, + (ChoiceRuntimeType)schema, factory()).get(); Verify.verify(choice instanceof ChoiceNodeCodecContext); return (ChoiceNodeCodecContext) choice; } @Override - protected Object deserializeObject(final NormalizedNode normalizedNode) { + protected Object deserializeObject(final NormalizedNode normalizedNode) { throw new UnsupportedOperationException("Unable to deserialize root"); } @@ -316,11 +365,10 @@ final class SchemaRootCodecContext extends DataContainerCo final List builder) { final Optional> caseType = arg.getCaseType(); if (caseType.isPresent()) { - // XXX: we use two caseType.get()s because of https://bugs.openjdk.java.net/browse/JDK-8144185, - // which makes JaCoCo blow up if we try using @NonNull on the local variable. - final ChoiceNodeCodecContext choice = choicesByClass.getUnchecked(caseType.get()); + final @NonNull Class type = caseType.orElseThrow(); + final ChoiceNodeCodecContext choice = choicesByClass.getUnchecked(type); choice.addYangPathArgument(arg, builder); - final DataContainerCodecContext caze = choice.streamChild(caseType.get()); + final DataContainerCodecContext caze = choice.streamChild(type); caze.addYangPathArgument(arg, builder); return caze.bindingPathArgumentChild(arg, builder); }