2 * Copyright (c) 2015 Cisco Systems, Inc. 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.controller.sal.connect.netconf.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkNotNull;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.FluentIterable;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.Iterables;
19 import com.google.common.collect.Lists;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.List;
26 import java.util.Map.Entry;
28 import java.util.concurrent.ConcurrentHashMap;
29 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
30 import org.opendaylight.yangtools.concepts.Identifiable;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
33 import org.opendaylight.yangtools.yang.data.api.Node;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
40 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
45 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
46 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.AttributesBuilder;
47 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
48 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder;
49 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
50 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
52 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
53 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
54 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
56 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
57 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
60 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
63 * Transforms an instance of yang instance identifier to a filter like structure in normalized node format. Can be also used to nest the edit-config rpc content.
64 * For each argument of the id, a specific normalized node is created to ensure schema context conformance.
66 public abstract class InstanceIdToNodes<T extends PathArgument> implements Identifiable<T> {
68 private final T identifier;
71 public T getIdentifier() {
75 protected InstanceIdToNodes(final T identifier) {
76 this.identifier = identifier;
79 abstract InstanceIdToNodes<?> getChild(final PathArgument child) throws DataNormalizationException;
81 public abstract NormalizedNode<?, ?> create(YangInstanceIdentifier legacyData, Optional<NormalizedNode<?, ?>> deepestChild, Optional<ModifyAction> operation);
83 private static abstract class SimpleTypeNormalization<T extends PathArgument> extends InstanceIdToNodes<T> {
85 protected SimpleTypeNormalization(final T identifier) {
90 public NormalizedNode<?, ?> create(final YangInstanceIdentifier id, final Optional<NormalizedNode<?, ?>> deepestChild, final Optional<ModifyAction> operation) {
92 final PathArgument pathArgument = Iterables.get(id.getPathArguments(), 0);
93 final NormalizedNodeAttrBuilder<? extends PathArgument, Object, ? extends NormalizedNode<? extends PathArgument, Object>> builder = getBuilder(pathArgument);
95 if(deepestChild.isPresent()) {
96 builder.withValue(deepestChild.get().getValue());
99 addModifyOpIfPresent(operation, builder);
100 return builder.build();
103 protected abstract NormalizedNodeAttrBuilder<? extends PathArgument, Object, ? extends NormalizedNode<? extends PathArgument, Object>> getBuilder(PathArgument node);
106 public InstanceIdToNodes<?> getChild(final PathArgument child) {
112 public void addModifyOpIfPresent(final Optional<ModifyAction> operation, final AttributesBuilder<?> builder) {
113 if(operation.isPresent()) {
114 builder.withAttributes(Collections.singletonMap(NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME, NetconfMessageTransformUtil.modifyOperationToXmlString(operation.get())));
118 private static final class LeafNormalization extends SimpleTypeNormalization<NodeIdentifier> {
120 protected LeafNormalization(final LeafSchemaNode potential) {
121 super(new NodeIdentifier(potential.getQName()));
125 protected NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> getBuilder(final PathArgument node) {
126 return Builders.leafBuilder().withNodeIdentifier(getIdentifier());
130 private static final class LeafListEntryNormalization extends SimpleTypeNormalization<NodeWithValue> {
132 public LeafListEntryNormalization(final LeafListSchemaNode potential) {
133 super(new NodeWithValue(potential.getQName(), null));
137 protected NormalizedNodeAttrBuilder<NodeWithValue, Object, LeafSetEntryNode<Object>> getBuilder(final PathArgument node) {
138 Preconditions.checkArgument(node instanceof NodeWithValue);
139 return Builders.leafSetEntryBuilder().withNodeIdentifier((NodeWithValue) node).withValue(((NodeWithValue) node).getValue());
144 private static abstract class CompositeNodeNormalizationOperation<T extends PathArgument> extends
145 InstanceIdToNodes<T> {
147 protected CompositeNodeNormalizationOperation(final T identifier) {
152 @SuppressWarnings("unchecked")
153 public final NormalizedNode<?, ?> create(final YangInstanceIdentifier id, final Optional<NormalizedNode<?, ?>> lastChild, final Optional<ModifyAction> operation) {
155 final Iterator<PathArgument> iterator = id.getPathArguments().iterator();
156 final PathArgument legacyData = iterator.next();
158 if (!isMixin(this) && getIdentifier().getNodeType() != null) {
159 checkArgument(getIdentifier().getNodeType().equals(legacyData.getNodeType()),
160 "Node QName must be %s was %s", getIdentifier().getNodeType(), legacyData.getNodeType());
162 final NormalizedNodeContainerBuilder builder = createBuilder(legacyData);
164 if (iterator.hasNext()) {
165 final PathArgument childPath = iterator.next();
166 final InstanceIdToNodes childOp = getChildOperation(childPath);
168 final YangInstanceIdentifier childId = YangInstanceIdentifier.create(Iterables.skip(id.getPathArguments(), 1));
169 builder.addChild(childOp.create(childId, lastChild, operation));
170 } else if(lastChild.isPresent()) {
171 builder.withValue(Lists.newArrayList((Collection<?>) lastChild.get().getValue()));
172 if(operation.isPresent()) {
173 Preconditions.checkArgument(builder instanceof AttributesBuilder<?>);
174 addModifyOpIfPresent(operation, ((AttributesBuilder<?>) builder));
178 return builder.build();
181 private InstanceIdToNodes getChildOperation(final PathArgument childPath) {
182 final InstanceIdToNodes childOp;
184 childOp = getChild(childPath);
185 } catch (final DataNormalizationException e) {
186 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
188 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
192 @SuppressWarnings("rawtypes")
193 protected abstract NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode);
196 static boolean isMixin(final InstanceIdToNodes<?> op) {
197 return op instanceof MixinNormalizationOp;
200 private static abstract class DataContainerNormalizationOperation<T extends PathArgument> extends
201 CompositeNodeNormalizationOperation<T> {
203 private final DataNodeContainer schema;
204 private final Map<PathArgument, InstanceIdToNodes<?>> byArg;
206 protected DataContainerNormalizationOperation(final T identifier, final DataNodeContainer schema) {
208 this.schema = schema;
209 this.byArg = new ConcurrentHashMap<>();
213 public InstanceIdToNodes<?> getChild(final PathArgument child) throws DataNormalizationException {
214 InstanceIdToNodes<?> potential = byArg.get(child);
215 if (potential != null) {
218 potential = fromLocalSchema(child);
219 return register(potential);
222 private InstanceIdToNodes<?> fromLocalSchema(final PathArgument child) throws DataNormalizationException {
223 if (child instanceof AugmentationIdentifier) {
224 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
227 return fromSchemaAndQNameChecked(schema, child.getNodeType());
230 private InstanceIdToNodes<?> register(final InstanceIdToNodes<?> potential) {
231 if (potential != null) {
232 byArg.put(potential.getIdentifier(), potential);
238 private static final class ListItemNormalization extends
239 DataContainerNormalizationOperation<NodeIdentifierWithPredicates> {
241 protected ListItemNormalization(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
242 super(identifier, schema);
246 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument currentArg) {
247 final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = Builders
248 .mapEntryBuilder().withNodeIdentifier((NodeIdentifierWithPredicates) currentArg);
249 for (final Entry<QName, Object> keyValue : ((NodeIdentifierWithPredicates) currentArg).getKeyValues().entrySet()) {
250 builder.addChild(Builders.leafBuilder()
252 .withNodeIdentifier(new NodeIdentifier(keyValue.getKey())).withValue(keyValue.getValue())
260 private static final class UnkeyedListItemNormalization extends DataContainerNormalizationOperation<NodeIdentifier> {
262 protected UnkeyedListItemNormalization(final ListSchemaNode schema) {
263 super(new NodeIdentifier(schema.getQName()), schema);
267 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
268 return Builders.unkeyedListEntryBuilder().withNodeIdentifier(getIdentifier());
273 private static final class ContainerTransformation extends DataContainerNormalizationOperation<NodeIdentifier> {
275 protected ContainerTransformation(final ContainerSchemaNode schema) {
276 super(new NodeIdentifier(schema.getQName()), schema);
280 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
281 return Builders.containerBuilder().withNodeIdentifier(getIdentifier());
286 * Marker interface for Mixin nodes normalization operations
288 private interface MixinNormalizationOp {}
291 private static final class OrderedLeafListMixinNormalization extends UnorderedLeafListMixinNormalization {
294 public OrderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
299 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
300 return Builders.orderedLeafSetBuilder().withNodeIdentifier(getIdentifier());
304 private static class UnorderedLeafListMixinNormalization extends CompositeNodeNormalizationOperation<NodeIdentifier> implements MixinNormalizationOp {
306 private final InstanceIdToNodes<?> innerOp;
308 public UnorderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
309 super(new NodeIdentifier(potential.getQName()));
310 innerOp = new LeafListEntryNormalization(potential);
314 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
315 return Builders.leafSetBuilder().withNodeIdentifier(getIdentifier());
319 public InstanceIdToNodes<?> getChild(final PathArgument child) {
320 if (child instanceof NodeWithValue) {
327 private static final class AugmentationNormalization extends DataContainerNormalizationOperation<AugmentationIdentifier> implements MixinNormalizationOp {
329 public AugmentationNormalization(final AugmentationSchema augmentation, final DataNodeContainer schema) {
331 super(augmentationIdentifierFrom(augmentation), augmentationProxy(augmentation, schema));
335 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
336 return Builders.augmentationBuilder().withNodeIdentifier(getIdentifier());
340 private static class UnorderedMapMixinNormalization extends CompositeNodeNormalizationOperation<NodeIdentifier> implements MixinNormalizationOp {
342 private final ListItemNormalization innerNode;
344 public UnorderedMapMixinNormalization(final ListSchemaNode list) {
345 super(new NodeIdentifier(list.getQName()));
346 this.innerNode = new ListItemNormalization(new NodeIdentifierWithPredicates(list.getQName(),
347 Collections.<QName, Object>emptyMap()), list);
351 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
352 return Builders.mapBuilder().withNodeIdentifier(getIdentifier());
356 public InstanceIdToNodes<?> getChild(final PathArgument child) {
357 if (child.getNodeType().equals(getIdentifier().getNodeType())) {
364 private static class UnkeyedListMixinNormalization extends CompositeNodeNormalizationOperation<NodeIdentifier> implements MixinNormalizationOp {
366 private final UnkeyedListItemNormalization innerNode;
368 public UnkeyedListMixinNormalization(final ListSchemaNode list) {
369 super(new NodeIdentifier(list.getQName()));
370 this.innerNode = new UnkeyedListItemNormalization(list);
374 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
375 return Builders.unkeyedListBuilder().withNodeIdentifier(getIdentifier());
379 public InstanceIdToNodes<?> getChild(final PathArgument child) {
380 if (child.getNodeType().equals(getIdentifier().getNodeType())) {
388 private static final class OrderedMapMixinNormalization extends UnorderedMapMixinNormalization {
390 public OrderedMapMixinNormalization(final ListSchemaNode list) {
395 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
396 return Builders.orderedMapBuilder().withNodeIdentifier(getIdentifier());
401 private static class ChoiceNodeNormalization extends CompositeNodeNormalizationOperation<NodeIdentifier> implements MixinNormalizationOp {
403 private final ImmutableMap<PathArgument, InstanceIdToNodes<?>> byArg;
405 protected ChoiceNodeNormalization(final org.opendaylight.yangtools.yang.model.api.ChoiceNode schema) {
406 super(new NodeIdentifier(schema.getQName()));
407 final ImmutableMap.Builder<PathArgument, InstanceIdToNodes<?>> byArgBuilder = ImmutableMap.builder();
409 for (final ChoiceCaseNode caze : schema.getCases()) {
410 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
411 final InstanceIdToNodes<?> childOp = fromDataSchemaNode(cazeChild);
412 byArgBuilder.put(childOp.getIdentifier(), childOp);
415 byArg = byArgBuilder.build();
419 public InstanceIdToNodes<?> getChild(final PathArgument child) {
420 return byArg.get(child);
424 protected NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(final PathArgument compositeNode) {
425 return Builders.choiceBuilder().withNodeIdentifier(getIdentifier());
429 private static class AnyXmlNormalization extends InstanceIdToNodes<NodeIdentifier> {
431 protected AnyXmlNormalization(final AnyXmlSchemaNode schema) {
432 super(new NodeIdentifier(schema.getQName()));
436 public InstanceIdToNodes<?> getChild(final PathArgument child) throws DataNormalizationException {
441 public NormalizedNode<?, ?> create(final YangInstanceIdentifier legacyData, final Optional<NormalizedNode<?, ?>> deepestChild, final Optional<ModifyAction> operation) {
442 if(deepestChild.isPresent()) {
443 Preconditions.checkState(deepestChild instanceof AnyXmlNode);
444 final NormalizedNodeAttrBuilder<NodeIdentifier, Node<?>, AnyXmlNode> anyXmlBuilder =
445 Builders.anyXmlBuilder().withNodeIdentifier(getIdentifier()).withValue(((AnyXmlNode) deepestChild).getValue());
446 addModifyOpIfPresent(operation, anyXmlBuilder);
447 return anyXmlBuilder.build();
450 final NormalizedNodeAttrBuilder<NodeIdentifier, Node<?>, AnyXmlNode> builder =
451 Builders.anyXmlBuilder().withNodeIdentifier(getIdentifier());
452 return builder.build();
457 private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
458 DataSchemaNode potential = parent.getDataChildByName(child);
459 if (potential == null) {
460 final Iterable<org.opendaylight.yangtools.yang.model.api.ChoiceNode> choices = FluentIterable.from(
461 parent.getChildNodes()).filter(org.opendaylight.yangtools.yang.model.api.ChoiceNode.class);
462 potential = findChoice(choices, child);
464 return Optional.fromNullable(potential);
467 private static InstanceIdToNodes<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) throws DataNormalizationException {
468 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
469 if (!potential.isPresent()) {
470 throw new DataNormalizationException(String.format("Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema, schema.getChildNodes()));
473 final DataSchemaNode result = potential.get();
474 // We try to look up if this node was added by augmentation
475 if ((schema instanceof DataSchemaNode) && result.isAugmenting()) {
476 return fromAugmentation(schema, (AugmentationTarget) schema, result);
478 return fromDataSchemaNode(result);
481 private static org.opendaylight.yangtools.yang.model.api.ChoiceNode findChoice(
482 final Iterable<org.opendaylight.yangtools.yang.model.api.ChoiceNode> choices, final QName child) {
483 org.opendaylight.yangtools.yang.model.api.ChoiceNode foundChoice = null;
485 for (final org.opendaylight.yangtools.yang.model.api.ChoiceNode choice : choices) {
486 for (final ChoiceCaseNode caze : choice.getCases()) {
487 if (findChildSchemaNode(caze, child).isPresent()) {
488 foundChoice = choice;
496 private static AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchema augmentation) {
497 final ImmutableSet.Builder<QName> potentialChildren = ImmutableSet.builder();
498 for (final DataSchemaNode child : augmentation.getChildNodes()) {
499 potentialChildren.add(child.getQName());
501 return new AugmentationIdentifier(potentialChildren.build());
504 private static DataNodeContainer augmentationProxy(final AugmentationSchema augmentation, final DataNodeContainer schema) {
505 final Set<DataSchemaNode> children = new HashSet<>();
506 for (final DataSchemaNode augNode : augmentation.getChildNodes()) {
507 children.add(schema.getDataChildByName(augNode.getQName()));
509 return new NodeContainerProxy(null, children);
513 * Returns a SchemaPathUtil for provided child node
515 * If supplied child is added by Augmentation this operation returns
516 * a SchemaPathUtil for augmentation,
517 * otherwise returns a SchemaPathUtil for child as
518 * call for {@link #fromDataSchemaNode(org.opendaylight.yangtools.yang.model.api.DataSchemaNode)}.
520 private static InstanceIdToNodes<?> fromAugmentation(final DataNodeContainer parent,
521 final AugmentationTarget parentAug, final DataSchemaNode child) {
522 AugmentationSchema augmentation = null;
523 for (final AugmentationSchema aug : parentAug.getAvailableAugmentations()) {
524 final DataSchemaNode potential = aug.getDataChildByName(child.getQName());
525 if (potential != null) {
531 if (augmentation != null) {
532 return new AugmentationNormalization(augmentation, parent);
534 return fromDataSchemaNode(child);
538 private static InstanceIdToNodes<?> fromDataSchemaNode(final DataSchemaNode potential) {
539 if (potential instanceof ContainerSchemaNode) {
540 return new ContainerTransformation((ContainerSchemaNode) potential);
541 } else if (potential instanceof ListSchemaNode) {
542 return fromListSchemaNode((ListSchemaNode) potential);
543 } else if (potential instanceof LeafSchemaNode) {
544 return new LeafNormalization((LeafSchemaNode) potential);
545 } else if (potential instanceof org.opendaylight.yangtools.yang.model.api.ChoiceNode) {
546 return new ChoiceNodeNormalization((org.opendaylight.yangtools.yang.model.api.ChoiceNode) potential);
547 } else if (potential instanceof LeafListSchemaNode) {
548 return fromLeafListSchemaNode((LeafListSchemaNode) potential);
549 } else if (potential instanceof AnyXmlSchemaNode) {
550 return new AnyXmlNormalization((AnyXmlSchemaNode) potential);
555 private static InstanceIdToNodes<?> fromListSchemaNode(final ListSchemaNode potential) {
556 final List<QName> keyDefinition = potential.getKeyDefinition();
557 if (keyDefinition == null || keyDefinition.isEmpty()) {
558 return new UnkeyedListMixinNormalization(potential);
560 if (potential.isUserOrdered()) {
561 return new OrderedMapMixinNormalization(potential);
563 return new UnorderedMapMixinNormalization(potential);
566 private static InstanceIdToNodes<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
567 if (potential.isUserOrdered()) {
568 return new OrderedLeafListMixinNormalization(potential);
570 return new UnorderedLeafListMixinNormalization(potential);
573 public static NormalizedNode<?, ?> serialize(final SchemaContext ctx, final YangInstanceIdentifier id) {
574 return serialize(ctx, id, Optional.<NormalizedNode<?, ?>>absent(), Optional.<ModifyAction>absent());
577 public static NormalizedNode<?, ?> serialize(final SchemaContext ctx, final YangInstanceIdentifier id, final NormalizedNode<?, ?> deepestElement) {
578 return serialize(ctx, id, Optional.<NormalizedNode<?, ?>>of(deepestElement), Optional.<ModifyAction>absent());
581 public static NormalizedNode<?, ?> serialize(final SchemaContext ctx, final YangInstanceIdentifier id, final Optional<NormalizedNode<?, ?>> deepestElement, final Optional<ModifyAction> operation) {
582 Preconditions.checkNotNull(ctx);
583 Preconditions.checkNotNull(id);
584 final PathArgument topLevelElement = id.getPathArguments().iterator().next();
585 final DataSchemaNode dataChildByName = ctx.getDataChildByName(topLevelElement.getNodeType());
586 Preconditions.checkNotNull(dataChildByName, "Cannot find %s node in schema context. Instance identifier has to start from root", topLevelElement);
588 final InstanceIdToNodes<?> instanceIdToNodes = fromSchemaAndQNameChecked(ctx, topLevelElement.getNodeType());
589 return instanceIdToNodes.create(id, deepestElement, operation);
590 } catch (final DataNormalizationException e) {
591 throw new IllegalArgumentException("Unable to serialize: " + id, e);