From: Tony Tkacik Date: Wed, 29 Apr 2015 13:35:08 +0000 (+0200) Subject: Bug 3067: Improved error reporting in Binding Data Codec X-Git-Tag: release/lithium~103^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=3f754cccb393b989bafb8b194edf9da0ec3e9e8a;p=yangtools.git Bug 3067: Improved error reporting in Binding Data Codec Error reporting in Binding Data Codec was reworked to throw three additional subclasses of IllegalArgumentException for specific case of failure: MissingSchema and MissingSchemaForClass - exception thrown when schema context associated with codec does not contain models for supplied class or DOM argument. IncorrectNesting is thrown when schema is available an user constructed data with invalid nesting (bypassed generic compile-time checks). The checks to determine type of exception are done only if error condition is detected, so non-error fast path should not be affected by advanced checks. Added test which tests these types of exceptions. Change-Id: Iad020a42317ab46df4d2240568fd6e8205383857 Signed-off-by: Tony Tkacik --- diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ChoiceNodeCodecContext.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ChoiceNodeCodecContext.java index 5240ecf610..7ab7e6b346 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ChoiceNodeCodecContext.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ChoiceNodeCodecContext.java @@ -23,6 +23,7 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.binding.util.BindingReflections; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; @@ -39,27 +40,27 @@ final class ChoiceNodeCodecContext extends DataContainerCo public ChoiceNodeCodecContext(final DataContainerCodecPrototype prototype) { super(prototype); - Map> byYangCaseChildBuilder = new HashMap<>(); - Map, DataContainerCodecPrototype> byClassBuilder = new HashMap<>(); - Map, DataContainerCodecPrototype> byCaseChildClassBuilder = new HashMap<>(); - Set> potentialSubstitutions = new HashSet<>(); + final Map> byYangCaseChildBuilder = new HashMap<>(); + final Map, DataContainerCodecPrototype> byClassBuilder = new HashMap<>(); + final Map, DataContainerCodecPrototype> byCaseChildClassBuilder = new HashMap<>(); + final Set> potentialSubstitutions = new HashSet<>(); // Walks all cases for supplied choice in current runtime context - for (Class caze : factory().getRuntimeContext().getCases(getBindingClass())) { + for (final Class caze : factory().getRuntimeContext().getCases(getBindingClass())) { // We try to load case using exact match thus name // and original schema must equals - DataContainerCodecPrototype cazeDef = loadCase(caze); + final DataContainerCodecPrototype cazeDef = loadCase(caze); // If we have case definition, this case is instantiated // at current location and thus is valid in context of parent choice if (cazeDef != null) { byClassBuilder.put(cazeDef.getBindingClass(), cazeDef); // Updates collection of case children @SuppressWarnings("unchecked") - Class cazeCls = (Class) caze; - for (Class cazeChild : BindingReflections.getChildrenClasses(cazeCls)) { + final Class cazeCls = (Class) caze; + for (final Class cazeChild : BindingReflections.getChildrenClasses(cazeCls)) { byCaseChildClassBuilder.put(cazeChild, cazeDef); } // Updates collection of YANG instance identifier to case - for (DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) { + for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) { byYangCaseChildBuilder.put(new NodeIdentifier(cazeChild.getQName()), cazeDef); } } else { @@ -71,7 +72,7 @@ final class ChoiceNodeCodecContext extends DataContainerCo } } - Map, DataContainerCodecPrototype> bySubstitutionBuilder = new HashMap<>(); + final Map, DataContainerCodecPrototype> bySubstitutionBuilder = new HashMap<>(); /* * Walks all cases which are not directly instantiated and * tries to match them to instantiated cases - represent same data as instantiated case, @@ -79,8 +80,8 @@ final class ChoiceNodeCodecContext extends DataContainerCo * binding specification, that if choice is in grouping schema path location is lost, * and users may use incorrect case class using copy builders. */ - for(Class substitution : potentialSubstitutions) { - search: for(Entry, DataContainerCodecPrototype> real : byClassBuilder.entrySet()) { + for(final Class substitution : potentialSubstitutions) { + search: for(final Entry, DataContainerCodecPrototype> real : byClassBuilder.entrySet()) { if(BindingReflections.isSubstitutionFor(substitution, real.getKey())) { bySubstitutionBuilder.put(substitution, real.getValue()); break search; @@ -96,18 +97,17 @@ final class ChoiceNodeCodecContext extends DataContainerCo @SuppressWarnings("unchecked") @Override - public DataContainerCodecContext streamChild(Class childClass) { - DataContainerCodecPrototype child = byClass.get(childClass); - Preconditions.checkArgument(child != null,"Supplied class is not valid case",childClass); - return (DataContainerCodecContext) child.get(); + public DataContainerCodecContext streamChild(final Class childClass) { + final DataContainerCodecPrototype child = byClass.get(childClass); + return (DataContainerCodecContext) childNonNull(child,childClass,"Supplied class %s is not valid case").get(); } @SuppressWarnings("unchecked") @Override public Optional> possibleStreamChild( - Class childClass) { - DataContainerCodecPrototype child = byClass.get(childClass); + final Class childClass) { + final DataContainerCodecPrototype child = byClass.get(childClass); if(child != null) { return Optional.>of((DataContainerCodecContext) child.get()); } @@ -119,7 +119,7 @@ final class ChoiceNodeCodecContext extends DataContainerCo } protected DataContainerCodecPrototype loadCase(final Class childClass) { - Optional childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass); + final Optional childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass); if (childSchema.isPresent()) { return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory()); } @@ -130,48 +130,47 @@ final class ChoiceNodeCodecContext extends DataContainerCo @Override public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { - DataContainerCodecPrototype cazeProto = byYangCaseChild.get(arg); - Preconditions.checkArgument(cazeProto != null, "Argument %s is not valid child of %s", arg, schema()); + final DataContainerCodecPrototype cazeProto = byYangCaseChild.get(arg); + childNonNull(cazeProto != null, arg,"Argument %s is not valid child of %s", arg, schema()); return cazeProto.get().yangPathArgumentChild(arg); } @SuppressWarnings("unchecked") @Override + @Nullable public D deserialize(final NormalizedNode data) { - Preconditions - .checkArgument(data instanceof org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode); - NormalizedNodeContainer> casted = (NormalizedNodeContainer>) data; - NormalizedNode first = Iterables.getFirst(casted.getValue(), null); + Preconditions.checkArgument(data instanceof ChoiceNode); + final NormalizedNodeContainer> casted = (NormalizedNodeContainer>) data; + final NormalizedNode first = Iterables.getFirst(casted.getValue(), null); if (first == null) { return null; } - DataContainerCodecPrototype caze = byYangCaseChild.get(first.getIdentifier()); + final DataContainerCodecPrototype caze = byYangCaseChild.get(first.getIdentifier()); return (D) caze.get().deserialize(data); } - @Nullable DataContainerCodecContext getCazeByChildClass(final @Nonnull Class type) { - final DataContainerCodecPrototype protoCtx = byCaseChildClass.get(type); - if(protoCtx != null) { - return protoCtx.get(); - } - return null; + DataContainerCodecContext getCazeByChildClass(final @Nonnull Class type) { + final DataContainerCodecPrototype protoCtx = + childNonNull(byCaseChildClass.get(type), type, "Class %s is not child of any cases for %s", type, + bindingArg()); + return protoCtx.get(); } @Override - protected Object deserializeObject(NormalizedNode normalizedNode) { + protected Object deserializeObject(final NormalizedNode normalizedNode) { return deserialize(normalizedNode); } @Override - public PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) { + public PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { Preconditions.checkArgument(getDomPathArgument().equals(arg)); return null; } @Override public YangInstanceIdentifier.PathArgument serializePathArgument( - PathArgument arg) { + final PathArgument arg) { // FIXME: check for null, since binding container is null. return getDomPathArgument(); } diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java index 371a181ce4..ef21235d59 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java @@ -12,6 +12,7 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.util.List; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCachingCodec; import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter; @@ -19,6 +20,7 @@ import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.DataObjectSerializer; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; @@ -146,6 +148,36 @@ abstract class DataContainerCodecContext extends NodeCod return new BindingToNormalizedStreamWriter(this, domWriter); } + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final YangInstanceIdentifier.PathArgument child, + final String message, final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child); + throw IncorrectNestingException.create(message, args); + } + + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final QName child, final String message, + final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child); + throw IncorrectNestingException.create(message, args); + } + + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final Class childClass, final String message, + final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaForClassException.check(factory().getRuntimeContext(), childClass); + throw IncorrectNestingException.create(message, args); + } + DataObjectSerializer eventStreamSerializer() { if(eventStreamSerializer == null) { eventStreamSerializer = factory().getEventStreamSerializer(getBindingClass()); diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataObjectCodecContext.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataObjectCodecContext.java index 8302aed0e6..0ca1d2b850 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataObjectCodecContext.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataObjectCodecContext.java @@ -7,8 +7,6 @@ */ package org.opendaylight.yangtools.binding.data.codec.impl; -import org.opendaylight.yangtools.yang.binding.AugmentationHolder; - import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; @@ -33,6 +31,7 @@ import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy import org.opendaylight.yangtools.sal.binding.model.api.Type; import org.opendaylight.yangtools.yang.binding.Augmentable; import org.opendaylight.yangtools.yang.binding.Augmentation; +import org.opendaylight.yangtools.yang.binding.AugmentationHolder; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.util.BindingReflections; @@ -155,8 +154,7 @@ abstract class DataObjectCodecContext) childProto.get(); + return (DataContainerCodecContext) childNonNull(childProto, childClass, " Child %s is not valid child.").get(); } @@ -177,23 +175,17 @@ abstract class DataObjectCodecContext argType = arg.getType(); final DataContainerCodecPrototype ctxProto = byBindingArgClass.get(argType); - if(ctxProto != null) { - final DataContainerCodecContext context = ctxProto.get(); - if(context instanceof ChoiceNodeCodecContext) { - final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) context; - final DataContainerCodecContext caze = choice.getCazeByChildClass(arg.getType()); - if(caze != null) { - choice.addYangPathArgument(arg, builder); - caze.addYangPathArgument(arg, builder); - return caze.bindingPathArgumentChild(arg, builder); - } - return null; - } - context.addYangPathArgument(arg, builder); - return context; + final DataContainerCodecContext context = + childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get(); + if (context instanceof ChoiceNodeCodecContext) { + final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) context; + final DataContainerCodecContext caze = choice.getCazeByChildClass(arg.getType()); + choice.addYangPathArgument(arg, builder); + caze.addYangPathArgument(arg, builder); + return caze.bindingPathArgumentChild(arg, builder); } - // Argument is not valid child. - return null; + context.addYangPathArgument(arg, builder); + return context; } @SuppressWarnings("unchecked") @@ -203,14 +195,13 @@ abstract class DataObjectCodecContext) childSupplier.get(); } protected final LeafNodeCodecContext getLeafChild(final String name) { final LeafNodeCodecContext value = leafChild.get(name); - Preconditions.checkArgument(value != null, "Leaf %s is not valid for %s", name, getBindingClass()); - return value; + return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass()); } private DataContainerCodecPrototype loadChildPrototype(final Class childClass) { @@ -248,8 +239,9 @@ abstract class DataObjectCodecContext getAugmentationPrototype(final Type value) { diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java new file mode 100644 index 0000000000..5e7ec6a4ff --- /dev/null +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.binding.data.codec.impl; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * + * Thrown where incorrect nesting of data structures was detected + * and was caused by user. + * + */ +public class IncorrectNestingException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + protected IncorrectNestingException(final String message) { + super(message); + } + + public static IncorrectNestingException create(final String message, final Object... args) { + return new IncorrectNestingException(String.format(message, args)); + } + + public static void check(final boolean check, final String message, final Object... args) { + if(!check) { + throw IncorrectNestingException.create(message, args); + } + } + + @Nonnull + public static V checkNonNull(@Nullable final V nullable, final String message, final Object... args) { + if(nullable != null) { + return nullable; + } + throw IncorrectNestingException.create(message, args); + } +} diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/LeafNodeCodecContext.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/LeafNodeCodecContext.java index e8d115c16d..81d9f3a609 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/LeafNodeCodecContext.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/LeafNodeCodecContext.java @@ -64,58 +64,57 @@ final class LeafNodeCodecContext extends NodeCodecContext< } @Override - public BindingCodecTreeNode bindingPathArgumentChild(PathArgument arg, - List builder) { - return null; + public BindingCodecTreeNode bindingPathArgumentChild(final PathArgument arg, + final List builder) { + throw new IllegalArgumentException("Leaf does not have children"); } @Override public BindingNormalizedNodeCachingCodec createCachingCodec( - ImmutableCollection> cacheSpecifier) { - return null; + final ImmutableCollection> cacheSpecifier) { + throw new UnsupportedOperationException("Leaves does not support caching codec."); } @Override public Class getBindingClass() { - return null; + throw new UnsupportedOperationException("Leaf does not have DataObject representation"); } @Override - public NormalizedNode serialize(D data) { + public NormalizedNode serialize(final D data) { throw new UnsupportedOperationException("Separete serialization of leaf node is not supported."); } @Override - public void writeAsNormalizedNode(D data, NormalizedNodeStreamWriter writer) { + public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) { throw new UnsupportedOperationException("Separete serialization of leaf node is not supported."); } @Override - public BindingCodecTreeNode streamChild(Class childClass) { - return null; + public BindingCodecTreeNode streamChild(final Class childClass) { + throw new IllegalArgumentException("Leaf does not have children"); } @Override public Optional> possibleStreamChild( - Class childClass) { - return null; + final Class childClass) { + throw new IllegalArgumentException("Leaf does not have children"); } @Override - public BindingCodecTreeNode yangPathArgumentChild( - org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument child) { - return null; + public BindingCodecTreeNode yangPathArgumentChild(final YangInstanceIdentifier.PathArgument child) { + throw new IllegalArgumentException("Leaf does not have children"); } @Override - protected Object deserializeObject(NormalizedNode normalizedNode) { + protected Object deserializeObject(final NormalizedNode normalizedNode) { if (normalizedNode instanceof LeafNode) { return valueCodec.deserialize(normalizedNode.getValue()); } else if(normalizedNode instanceof LeafSetNode) { @SuppressWarnings("unchecked") - Collection> domValues = ((LeafSetNode) normalizedNode).getValue(); - List result = new ArrayList<>(domValues.size()); - for (LeafSetEntryNode valueNode : domValues) { + final Collection> domValues = ((LeafSetNode) normalizedNode).getValue(); + final List result = new ArrayList<>(domValues.size()); + for (final LeafSetEntryNode valueNode : domValues) { result.add(valueCodec.deserialize(valueNode.getValue())); } return result; @@ -124,13 +123,13 @@ final class LeafNodeCodecContext extends NodeCodecContext< } @Override - public InstanceIdentifier.PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) { + public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { Preconditions.checkArgument(getDomPathArgument().equals(arg)); return null; } @Override - public YangInstanceIdentifier.PathArgument serializePathArgument(InstanceIdentifier.PathArgument arg) { + public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) { return getDomPathArgument(); } diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java new file mode 100644 index 0000000000..5c5bac6be3 --- /dev/null +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.binding.data.codec.impl; + +import com.google.common.base.Preconditions; +import java.util.Set; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Thrown when codec was used with data which are not modeled + * and available in schema used by codec. + * + */ +public class MissingSchemaException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + protected MissingSchemaException(final String msg) { + super(msg); + } + + private static MissingSchemaException create(final String format, final Object... args) { + return new MissingSchemaException(String.format(format, args)); + } + + static void checkModulePresent(final SchemaContext schemaContext, final QName name) { + if(schemaContext.findModuleByNamespaceAndRevision(name.getNamespace(), name.getRevision()) == null) { + throw MissingSchemaException.create("Module %s is not present in current schema context.",name.getModule()); + } + } + + static void checkModulePresent(final SchemaContext schemaContext, final YangInstanceIdentifier.PathArgument child) { + checkModulePresent(schemaContext, extractName(child)); + } + + private static QName extractName(final PathArgument child) { + if(child instanceof YangInstanceIdentifier.AugmentationIdentifier) { + final Set children = ((YangInstanceIdentifier.AugmentationIdentifier) child).getPossibleChildNames(); + Preconditions.checkArgument(!children.isEmpty(),"Augmentation without childs must not be used in data"); + return children.iterator().next(); + } + return child.getNodeType(); + } + + + + +} diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java new file mode 100644 index 0000000000..6f9778f3ef --- /dev/null +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.binding.data.codec.impl; + +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext; +import org.opendaylight.yangtools.yang.binding.Augmentation; + +/** + * + * Thrown when Java Binding class was used in data for which codec does not + * have schema. + * + * By serialization / deserialization of this exception {@link #getBindingClass()} + * will return null. + * + */ +public class MissingSchemaForClassException extends MissingSchemaException { + + private static final long serialVersionUID = 1L; + + private final transient Class bindingClass; + + private MissingSchemaForClassException(final Class clz) { + super(String.format("Schema is not available for %s", clz)); + this.bindingClass = Preconditions.checkNotNull(clz); + } + + static MissingSchemaForClassException forClass(final Class clz) { + return new MissingSchemaForClassException(clz); + } + + public Class getBindingClass() { + return bindingClass; + } + + public static void check(final BindingRuntimeContext runtimeContext, final Class bindingClass) { + final Object schema; + if (Augmentation.class.isAssignableFrom(bindingClass)) { + schema = runtimeContext.getAugmentationDefinition(bindingClass); + } else { + schema = runtimeContext.getSchemaDefinition(bindingClass); + } + if(schema == null) { + throw MissingSchemaForClassException.forClass(bindingClass); + } + } +} diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/SchemaRootCodecContext.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/SchemaRootCodecContext.java index b2fb7d0867..16533f06a4 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/SchemaRootCodecContext.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/SchemaRootCodecContext.java @@ -9,9 +9,11 @@ package org.opendaylight.yangtools.binding.data.codec.impl; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; import org.opendaylight.yangtools.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.BindingMapping; import org.opendaylight.yangtools.yang.binding.ChildOf; @@ -70,9 +72,7 @@ final class SchemaRootCodecContext extends DataContainerCo @Override public DataContainerCodecContext load(final QName qname) { final DataSchemaNode childSchema = schema().getDataChildByName(qname); - Preconditions.checkArgument(childSchema != null, "Argument %s is not valid child of %s", qname, - schema()); - + childNonNull(childSchema, qname,"Argument %s is not valid child of %s", qname,schema()); if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) { @SuppressWarnings("rawtypes") final Class childCls = factory().getRuntimeContext().getClassForSchema(childSchema); @@ -126,7 +126,7 @@ final class SchemaRootCodecContext extends DataContainerCo @SuppressWarnings("unchecked") @Override - public DataContainerCodecContext streamChild(Class childClass) + public DataContainerCodecContext streamChild(final Class childClass) throws IllegalArgumentException { /* FIXME: This is still not solved for RPCs * TODO: Probably performance wise RPC, Data and Notification loading cache @@ -136,7 +136,7 @@ final class SchemaRootCodecContext extends DataContainerCo if (Notification.class.isAssignableFrom(childClass)) { return (DataContainerCodecContext) getNotification((Class)childClass); } - return (DataContainerCodecContext) childrenByClass.getUnchecked(childClass); + return (DataContainerCodecContext) getOrRethrow(childrenByClass,childClass); } @Override @@ -146,7 +146,7 @@ final class SchemaRootCodecContext extends DataContainerCo @Override public DataContainerCodecContext yangPathArgumentChild(final PathArgument arg) { - return childrenByQName.getUnchecked(arg.getNodeType()); + return getOrRethrow(childrenByQName,arg.getNodeType()); } @Override @@ -157,26 +157,26 @@ final class SchemaRootCodecContext extends DataContainerCo ContainerNodeCodecContext getRpc(final Class rpcInputOrOutput) { - return rpcDataByClass.getUnchecked(rpcInputOrOutput); + return getOrRethrow(rpcDataByClass, rpcInputOrOutput); } NotificationCodecContext getNotification(final Class notification) { - return notificationsByClass.getUnchecked(notification); + return getOrRethrow(notificationsByClass, notification); } NotificationCodecContext getNotification(final SchemaPath notification) { - return notificationsByPath.getUnchecked(notification); + return getOrRethrow(notificationsByPath, notification); } ContainerNodeCodecContext getRpc(final SchemaPath notification) { - return rpcDataByPath.getUnchecked(notification); + return getOrRethrow(rpcDataByPath, notification); } private DataContainerCodecContext createDataTreeChildContext(final Class key) { final Class parent = ClassLoaderUtils.findFirstGenericArgument(key, ChildOf.class); - Preconditions.checkArgument(DataRoot.class.isAssignableFrom(parent)); + IncorrectNestingException.check(DataRoot.class.isAssignableFrom(parent), "Class %s is not top level item.", key); final QName qname = BindingReflections.findQName(key); - final DataSchemaNode childSchema = schema().getDataChildByName(qname); + final DataSchemaNode childSchema = childNonNull(schema().getDataChildByName(qname),key,"%s is not top-level item.",key); return DataContainerCodecPrototype.from(key, childSchema, factory()).get(); } @@ -226,20 +226,32 @@ final class SchemaRootCodecContext extends DataContainerCo } @Override - protected Object deserializeObject(NormalizedNode normalizedNode) { + protected Object deserializeObject(final NormalizedNode normalizedNode) { throw new UnsupportedOperationException("Unable to deserialize root"); } @Override - public InstanceIdentifier.PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) { + public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { Preconditions.checkArgument(arg == null); return null; } @Override - public YangInstanceIdentifier.PathArgument serializePathArgument(InstanceIdentifier.PathArgument arg) { + public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) { Preconditions.checkArgument(arg == null); return null; } + private static V getOrRethrow(final LoadingCache cache, final K key) { + try { + return cache.getUnchecked(key); + } catch (final UncheckedExecutionException e) { + final Throwable cause = e.getCause(); + if(cause != null) { + Throwables.propagateIfPossible(cause); + } + throw e; + } + } + } \ No newline at end of file diff --git a/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java b/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java new file mode 100644 index 0000000000..4a565ede60 --- /dev/null +++ b/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.binding.data.codec.test; + +import javassist.ClassPool; +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.TreeLeafOnlyAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelListKey; +import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator; +import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; +import org.opendaylight.yangtools.binding.data.codec.impl.IncorrectNestingException; +import org.opendaylight.yangtools.binding.data.codec.impl.MissingSchemaException; +import org.opendaylight.yangtools.binding.data.codec.impl.MissingSchemaForClassException; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext; +import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + + +public class ExceptionReportingTest { + + + private static final BindingNormalizedNodeCodecRegistry EMPTY_SCHEMA_CODEC = codec(); + private static final BindingNormalizedNodeCodecRegistry ONLY_TOP_CODEC = codec(Top.class); + private static final BindingNormalizedNodeCodecRegistry FULL_CODEC = codec(TreeComplexUsesAugment.class); + + private static final TopLevelListKey TOP_FOO_KEY = new TopLevelListKey("foo"); + private static final InstanceIdentifier BA_TOP_LEVEL_LIST = InstanceIdentifier + .builder(Top.class).child(TopLevelList.class, TOP_FOO_KEY).build(); + private static final InstanceIdentifier BA_TREE_LEAF_ONLY = + BA_TOP_LEVEL_LIST.augmentation(TreeLeafOnlyAugment.class); + + private static final QName TOP_QNAME = + QName.create("urn:opendaylight:params:xml:ns:yang:yangtools:test:binding", "2014-07-01", "top"); + private static final YangInstanceIdentifier BI_TOP_PATH = YangInstanceIdentifier.builder().node(TOP_QNAME).build(); + private static final YangInstanceIdentifier BI_TREE_LEAF_ONLY = FULL_CODEC.toYangInstanceIdentifier(BA_TREE_LEAF_ONLY); + + + @Test(expected=MissingSchemaException.class) + public void testDOMTop() { + EMPTY_SCHEMA_CODEC.fromYangInstanceIdentifier(BI_TOP_PATH); + } + + @Test(expected=MissingSchemaException.class) + public void testDOMAugment() { + EMPTY_SCHEMA_CODEC.fromYangInstanceIdentifier(BI_TREE_LEAF_ONLY); + } + + @Test(expected=MissingSchemaForClassException.class) + public void testBindingTop() { + EMPTY_SCHEMA_CODEC.toYangInstanceIdentifier(BA_TOP_LEVEL_LIST); + } + + @Test(expected=MissingSchemaForClassException.class) + public void testBindingAugment() { + ONLY_TOP_CODEC.toYangInstanceIdentifier(BA_TREE_LEAF_ONLY); + } + + @Test(expected=IncorrectNestingException.class) + public void testBindingSkippedRoot() { + FULL_CODEC.toYangInstanceIdentifier(InstanceIdentifier.create(TopLevelList.class)); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Test(expected=IncorrectNestingException.class) + public void testBindingIncorrectAugment() { + FULL_CODEC.toYangInstanceIdentifier(InstanceIdentifier.create(Top.class).augmentation((Class) TreeComplexUsesAugment.class)); + } + + + private static BindingNormalizedNodeCodecRegistry codec(final Class... classes ) { + final ModuleInfoBackedContext ctx = ModuleInfoBackedContext.create(); + for(final Class clazz : classes) { + YangModuleInfo modInfo; + try { + modInfo = BindingReflections.getModuleInfo(clazz); + ctx.registerModuleInfo(modInfo); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + final SchemaContext schema = ctx.tryToCreateSchemaContext().get(); + final BindingRuntimeContext runtimeCtx = BindingRuntimeContext.create(ctx, schema); + final JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault()); + final BindingNormalizedNodeCodecRegistry registry = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(utils)); + registry.onBindingRuntimeContextUpdated(runtimeCtx); + return registry; + } + +} diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/BindingRuntimeContext.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/BindingRuntimeContext.java index 0db4b7da8f..e92f1ca0d0 100644 --- a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/BindingRuntimeContext.java +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/BindingRuntimeContext.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import javax.annotation.Nullable; import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy; @@ -40,7 +41,6 @@ import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTy import org.opendaylight.yangtools.yang.binding.Augmentation; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.AugmentationSchemaProxy; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; @@ -51,6 +51,7 @@ import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema; import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -163,14 +164,12 @@ public class BindingRuntimeContext implements Immutable { * schema. * * @param augClass Augmentation class - * @return Schema of augmentation - * @throws IllegalArgumentException If supplied class is not an augmentation or current context does not contain schema for augmentation. + * @return Schema of augmentation or null if augmentaiton is not known in this context + * @throws IllegalArgumentException If supplied class is not an augmentation */ - public AugmentationSchema getAugmentationDefinition(final Class augClass) throws IllegalArgumentException { + public @Nullable AugmentationSchema getAugmentationDefinition(final Class augClass) throws IllegalArgumentException { Preconditions.checkArgument(Augmentation.class.isAssignableFrom(augClass), "Class %s does not represent augmentation", augClass); - final AugmentationSchema ret = augmentationToSchema.get(referencedType(augClass)); - Preconditions.checkArgument(ret != null, "Supplied augmentation %s is not valid in current context", augClass); - return ret; + return augmentationToSchema.get(referencedType(augClass)); } /** @@ -195,6 +194,7 @@ public class BindingRuntimeContext implements Immutable { public Entry getResolvedAugmentationSchema(final DataNodeContainer target, final Class> aug) { final AugmentationSchema origSchema = getAugmentationDefinition(aug); + Preconditions.checkArgument(origSchema != null, "Augmentation %s is not known in current schema context",aug); /* * FIXME: Validate augmentation schema lookup * @@ -215,7 +215,7 @@ public class BindingRuntimeContext implements Immutable { } final AugmentationIdentifier identifier = new AugmentationIdentifier(childNames); - final AugmentationSchema proxy = new AugmentationSchemaProxy(origSchema, realChilds); + final AugmentationSchema proxy = new EffectiveAugmentationSchema(origSchema, realChilds); return new AbstractMap.SimpleEntry<>(identifier, proxy); }