Bug 1203: Reset state of LocationDispatchCodecs
[yangtools.git] / code-generator / binding-generator-impl / src / main / java / org / opendaylight / yangtools / sal / binding / generator / impl / LazyGeneratedCodecRegistry.java
index c251ba18203add8324274bf6a6a8e63563eb263e..22817ae3e1d70180a015fb448e0dfbeaa6d99414 100644 (file)
@@ -100,6 +100,10 @@ class LazyGeneratedCodecRegistry implements //
             .synchronizedMap(new WeakHashMap<Class<?>, AugmentableDispatchCodec>());
     private static final Map<Class<?>, AugmentationCodecWrapper<?>> augmentationCodecs = Collections
             .synchronizedMap(new WeakHashMap<Class<?>, AugmentationCodecWrapper<?>>());
+
+    private static final Map<Class<?>, LocationAwareDispatchCodec<?>> dispatchCodecs = Collections
+            .synchronizedMap(new WeakHashMap<Class<?>, LocationAwareDispatchCodec<?>>());
+
     private static final Map<Class<?>, QName> identityQNames = Collections
             .synchronizedMap(new WeakHashMap<Class<?>, QName>());
     private static final Map<QName, Type> qnamesToIdentityMap = new ConcurrentHashMap<>();
@@ -393,14 +397,9 @@ class LazyGeneratedCodecRegistry implements //
         Preconditions.checkState(caseSchema != null, "Case schema is not available for %s", caseClass.getName());
         Class<? extends BindingCodec> newCodec = generator.caseCodecFor(caseClass, caseSchema);
         BindingCodec newInstance = newInstanceOf(newCodec);
+        @SuppressWarnings("unchecked")
         ChoiceCaseCodecImpl caseCodec = new ChoiceCaseCodecImpl(caseClass, caseSchema, newInstance);
         caseCodecs.put(caseClass, caseCodec);
-
-        for (Entry<Class<?>, PublicChoiceCodecImpl<?>> choice : choiceCodecs.entrySet()) {
-            if (choice.getKey().isAssignableFrom(caseClass)) {
-                choice.getValue().cases.put(caseClass, caseCodec);
-            }
-        }
         return caseCodec;
     }
 
@@ -438,6 +437,25 @@ class LazyGeneratedCodecRegistry implements //
     @Override
     public void onGlobalContextUpdated(final SchemaContext context) {
         currentSchema = context;
+        resetDispatchCodecsAdaptation();
+
+    }
+
+    /**
+     * Resets / clears adaptation for all schema context sensitive codecs in
+     * order for them to adapt to new schema context and maybe newly discovered
+     * augmentations This ensure correct behaviour for augmentations and
+     * augmented cases for preexisting codecs, which augmentations were
+     * introduced at later point in time.
+     *
+     * This also makes removed augmentations unavailable.
+     */
+    private void resetDispatchCodecsAdaptation() {
+        synchronized (dispatchCodecs) {
+            for (LocationAwareDispatchCodec<?> codec : dispatchCodecs.values()) {
+                codec.resetAdaptation();
+            }
+        }
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
@@ -450,6 +468,9 @@ class LazyGeneratedCodecRegistry implements //
         PublicChoiceCodecImpl<?> newCodec = new PublicChoiceCodecImpl(delegate);
         DispatchChoiceCodecImpl dispatchCodec = new DispatchChoiceCodecImpl(choiceClass);
         choiceCodecs.put(choiceClass, newCodec);
+        synchronized (dispatchCodecs) {
+            dispatchCodecs.put(choiceClass, dispatchCodec);
+        }
         CodecMapping.setDispatchCodec(choiceCodec, dispatchCodec);
     }
 
@@ -478,6 +499,9 @@ class LazyGeneratedCodecRegistry implements //
         }
         ret = new AugmentableDispatchCodec(dataClass);
         augmentableCodecs.put(dataClass, ret);
+        synchronized (dispatchCodecs) {
+            dispatchCodecs.put(dataClass, ret);
+        }
         ret.tryToLoadImplementations();
         return ret;
     }
@@ -572,7 +596,7 @@ class LazyGeneratedCodecRegistry implements //
 
     private interface LocationAwareBindingCodec<P, I> extends BindingCodec<P, I> {
 
-        boolean isApplicable(InstanceIdentifier<?> location);
+        boolean isApplicable(InstanceIdentifier<?> parentPath, CompositeNode data);
 
         public Class<?> getDataType();
 
@@ -588,6 +612,25 @@ class LazyGeneratedCodecRegistry implements //
             return implementations;
         }
 
+        /**
+         * Resets codec adaptation based on location and schema context.
+         *
+         * This is required if new cases / augmentations were introduced or
+         * removed and first use of codec is triggered by invocation from DOM to
+         * Java, so the implementations may change and this may require loading
+         * of new codecs and/or removal of existing ones.
+         *
+         */
+        public synchronized void resetAdaptation() {
+            adaptedForPaths.clear();
+            resetAdaptationImpl();
+        }
+
+        protected void resetAdaptationImpl() {
+            // Intentionally NOOP, subclasses may specify their custom
+            // behaviour.
+        }
+
         protected void addImplementation(final T implementation) {
             implementations.put(implementation.getDataType(), implementation);
         }
@@ -597,6 +640,40 @@ class LazyGeneratedCodecRegistry implements //
             throw new UnsupportedOperationException("Invocation of deserialize without Tree location is unsupported");
         }
 
+        @Override
+        public final Object deserialize(final Object parent, final InstanceIdentifier parentPath) {
+            adaptForPath(parentPath);
+            Preconditions.checkArgument(parent instanceof CompositeNode, "node must be of CompositeNode type.");
+            CompositeNode parentData = (CompositeNode) parent;
+            ArrayList<T> applicable = new ArrayList<>(implementations.size());
+
+            /*
+             * Codecs are filtered to only ones, which
+             * are applicable in supplied parent context.
+             *
+             */
+            for (T impl : getImplementations().values()) {
+                @SuppressWarnings("unchecked")
+                boolean codecApplicable = impl.isApplicable(parentPath, parentData);
+                if (codecApplicable) {
+                    applicable.add(impl);
+                }
+            }
+            LOG.trace("{}: Deserializing mixins from {}, Schema Location {}, Applicable Codecs: {}, All Codecs: {}",this,parent,parentPath,applicable,getImplementations().values());
+
+            /* In case of none is applicable, we return
+             * null. Since there is no mixin which
+             * is applicable in this location.
+            */
+            if(applicable.isEmpty()) {
+                return null;
+            }
+            return deserializeImpl(parentData, parentPath, applicable);
+        }
+
+        protected abstract Object deserializeImpl(final CompositeNode input, final InstanceIdentifier<?> parentPath,
+                Iterable<T> applicableCodecs);
+
         @Override
         public Object serialize(final Object input) {
             Preconditions.checkArgument(input instanceof DataContainer);
@@ -620,6 +697,7 @@ class LazyGeneratedCodecRegistry implements //
             if (adaptedForPaths.contains(path)) {
                 return;
             }
+            LOG.info("Adapting mixin codec {} for path {}",this,path);
             /**
              * We search in schema context if the use of this location aware
              * codec (augmentable codec, case codec) makes sense on provided
@@ -677,12 +755,8 @@ class LazyGeneratedCodecRegistry implements //
         private final Map<InstanceIdentifier<?>, ChoiceCaseNode> instantiatedLocations;
         private final Class<?> dataType;
 
-        @Override
-        public boolean isApplicable(final InstanceIdentifier location) {
-            return instantiatedLocations.containsKey(location);
-        }
-
-        public ChoiceCaseCodecImpl(final Class<?> caseClass, final ChoiceCaseNode caseNode, final BindingCodec newInstance) {
+        public ChoiceCaseCodecImpl(final Class<?> caseClass, final ChoiceCaseNode caseNode,
+                final BindingCodec newInstance) {
             this.delegate = newInstance;
             this.dataType = caseClass;
             this.schema = caseNode;
@@ -755,7 +829,8 @@ class LazyGeneratedCodecRegistry implements //
             }
         }
 
-        public boolean isAcceptable(final InstanceIdentifier path, final CompositeNode input) {
+        @Override
+        public boolean isApplicable(final InstanceIdentifier path, final CompositeNode input) {
             ChoiceCaseNode instantiatedSchema = null;
             synchronized (instantiatedLocations) {
                 instantiatedSchema = instantiatedLocations.get(path);
@@ -765,6 +840,40 @@ class LazyGeneratedCodecRegistry implements //
             }
             return checkAgainstSchema(instantiatedSchema, input);
         }
+
+        protected boolean isAugmenting(final QName choiceName, final QName proposedQName) {
+            if (schema.isAugmenting()) {
+                return true;
+            }
+            // Choice QName
+            QName parentQName = Iterables.get(schema.getPath().getPathTowardsRoot(), 1);
+            if (!parentQName.getNamespace().equals(schema.getQName().getNamespace())) {
+                return true;
+            }
+            if (!parentQName.equals(choiceName)) {
+                // This item is instantiation of choice via uses in other YANG
+                // module
+                if (choiceName.getNamespace().equals(schema.getQName())) {
+                    // Original definition of grouping is in same namespace
+                    // as original definition of case
+                    // so for sure case is introduced via instantiation of
+                    // grouping
+                    return false;
+                }
+                // Since we are still here, that means case has same namespace
+                // as its parent, which is instantiation of grouping
+                // but case namespace is different from parent node
+                // so it is augmentation.
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "ChoiceCaseCodec [case=" + dataType
+                    + ", knownLocations=" + instantiatedLocations.keySet() + "]";
+        }
     }
 
     private static class PublicChoiceCodecImpl<T> implements ChoiceCodec<T>,
@@ -772,10 +881,6 @@ class LazyGeneratedCodecRegistry implements //
 
         private final BindingCodec<Map<QName, Object>, Object> delegate;
 
-        @SuppressWarnings("rawtypes")
-        private final Map<Class, ChoiceCaseCodecImpl<?>> cases = Collections
-                .synchronizedMap(new WeakHashMap<Class, ChoiceCaseCodecImpl<?>>());
-
         public PublicChoiceCodecImpl(final BindingCodec<Map<QName, Object>, Object> delegate) {
             this.delegate = delegate;
         }
@@ -812,21 +917,12 @@ class LazyGeneratedCodecRegistry implements //
         }
 
         @Override
-        public Object deserialize(final Object input, @SuppressWarnings("rawtypes") final InstanceIdentifier path) {
-            adaptForPath(path);
-
-            if (input instanceof CompositeNode) {
-                List<Entry<Class, ChoiceCaseCodecImpl<?>>> codecs = new ArrayList<>(getImplementations().entrySet());
-                for (Entry<Class, ChoiceCaseCodecImpl<?>> codec : codecs) {
-                    ChoiceCaseCodecImpl<?> caseCodec = codec.getValue();
-                    if (caseCodec.isAcceptable(path, (CompositeNode) input)) {
-                        ValueWithQName<?> value = caseCodec.deserialize((CompositeNode) input, path);
-                        if (value != null) {
-                            return value.getValue();
-                        }
-                        return null;
-                    }
-                }
+        public Object deserializeImpl(final CompositeNode input, final InstanceIdentifier<?> path,
+                final Iterable<ChoiceCaseCodecImpl<?>> codecs) {
+            ChoiceCaseCodecImpl<?> caseCodec = Iterables.getOnlyElement(codecs);
+            ValueWithQName<?> value = caseCodec.deserialize(input, path);
+            if (value != null) {
+                return value.getValue();
             }
             return null;
         }
@@ -836,29 +932,21 @@ class LazyGeneratedCodecRegistry implements //
         public Object serialize(final Object input) {
             Preconditions.checkArgument(input instanceof Map.Entry<?, ?>, "Input must be QName, Value");
             @SuppressWarnings("rawtypes")
+            QName derivedQName = (QName) ((Map.Entry) input).getKey();
+            @SuppressWarnings("rawtypes")
             Object inputValue = ((Map.Entry) input).getValue();
             Preconditions.checkArgument(inputValue instanceof DataObject);
             Class<? extends DataContainer> inputType = ((DataObject) inputValue).getImplementedInterface();
             ChoiceCaseCodecImpl<?> codec = tryToLoadImplementation(inputType);
             Preconditions.checkState(codec != null, "Unable to get codec for %s", inputType);
-            if(isAugmenting(codec.getSchema())) {
-                // If choice is augmenting we use QName which defined this augmentation
+            if (codec.isAugmenting(choiceName, derivedQName)) {
+                // If choice is augmenting we use QName which defined this
+                // augmentation
                 return codec.getDelegate().serialize(new ValueWithQName<>(codec.getSchema().getQName(), inputValue));
             }
             return codec.getDelegate().serialize(input);
         }
 
-        private boolean isAugmenting(final ChoiceCaseNode schema) {
-            if(schema.isAugmenting()) {
-                return true;
-            }
-            QName parentQName = Iterables.get(schema.getPath().getPathTowardsRoot(), 2); // choice QName
-            if(!parentQName.getNamespace().equals(schema.getQName().getNamespace())) {
-                return true;
-            }
-            return false;
-        }
-
         @SuppressWarnings("rawtypes")
         protected Optional<ChoiceCaseCodecImpl> tryToLoadImplementation(final Type potential) {
             try {
@@ -899,44 +987,25 @@ class LazyGeneratedCodecRegistry implements //
 
         @Override
         protected void adaptForPathImpl(final InstanceIdentifier<?> augTarget, final DataNodeContainer ctxNode) {
-            Optional<ChoiceNode> newChoice = findInstantiatedChoice(ctxNode, choiceName);
+            Optional<ChoiceNode> newChoice = BindingSchemaContextUtils.findInstantiatedChoice(ctxNode, choiceType);
             tryToLoadImplementations();
-            if (newChoice.isPresent()) {
-                for (@SuppressWarnings("rawtypes")
-                Entry<Class, ChoiceCaseCodecImpl<?>> codec : getImplementations().entrySet()) {
-                    ChoiceCaseCodecImpl<?> caseCodec = codec.getValue();
-                    Optional<ChoiceCaseNode> instantiatedSchema = findInstantiatedCase(newChoice.get(),
-                            caseCodec.getSchema());
-                    if (instantiatedSchema.isPresent()) {
-                        caseCodec.adaptForPath(augTarget, instantiatedSchema.get());
-                    }
+            Preconditions.checkState(newChoice.isPresent(), "BUG: Unable to find instantiated choice node in schema.");
+            for (@SuppressWarnings("rawtypes")
+            Entry<Class, ChoiceCaseCodecImpl<?>> codec : getImplementations().entrySet()) {
+                ChoiceCaseCodecImpl<?> caseCodec = codec.getValue();
+                Optional<ChoiceCaseNode> instantiatedSchema = BindingSchemaContextUtils.findInstantiatedCase(newChoice.get(),
+                        caseCodec.getSchema());
+                if (instantiatedSchema.isPresent()) {
+                    caseCodec.adaptForPath(augTarget, instantiatedSchema.get());
                 }
             }
         }
 
-        private Optional<ChoiceNode> findInstantiatedChoice(final DataNodeContainer ctxNode, final QName choiceName) {
-            DataSchemaNode potential = ctxNode.getDataChildByName(choiceName);
-            if (potential == null) {
-                potential = ctxNode.getDataChildByName(choiceName.getLocalName());
-            }
 
-            if (potential instanceof ChoiceNode) {
-                return Optional.of((ChoiceNode) potential);
-            }
-
-            return Optional.absent();
-        }
 
-        private Optional<ChoiceCaseNode> findInstantiatedCase(final ChoiceNode newChoice, final ChoiceCaseNode schema) {
-            ChoiceCaseNode potential = newChoice.getCaseNodeByName(schema.getQName());
-            if (potential != null) {
-                return Optional.of(potential);
-            }
-            // FIXME: Probably requires more extensive check
-            // e.g. we have one choice and two augmentations from different
-            // modules using same local name
-            // but different namespace / contents
-            return Optional.fromNullable(newChoice.getCaseNodeByName(schema.getQName().getLocalName()));
+        @Override
+        public String toString() {
+            return "DispatchChoiceCodecImpl [choiceType=" + choiceType + "]";
         }
     }
 
@@ -985,24 +1054,19 @@ class LazyGeneratedCodecRegistry implements //
         }
 
         @Override
-        public Map<Class, Augmentation> deserialize(final Object input, final InstanceIdentifier path) {
-            adaptForPath(path);
+        public Map<Class, Augmentation> deserializeImpl(final CompositeNode input, final InstanceIdentifier<?> path,
+                final Iterable<AugmentationCodecWrapper> codecs) {
+            LOG.trace("{}: Going to deserialize augmentations from {} in location {}. Available codecs {}",this,input,path,codecs);
             Map<Class, Augmentation> ret = new HashMap<>();
-
-            if (input instanceof CompositeNode) {
-                List<Entry<Class, AugmentationCodecWrapper>> codecs = new ArrayList<>(getImplementations().entrySet());
-                for (Entry<Class, AugmentationCodecWrapper> codec : codecs) {
-                    AugmentationCodec<?> ac = codec.getValue();
-                    if (ac.isAcceptable(path)) {
-                        // We add Augmentation Identifier to path, in order to
-                        // correctly identify children.
-                        InstanceIdentifier augmentPath = path.builder().augmentation(codec.getKey()).build();
-                        ValueWithQName<?> value = codec.getValue().deserialize((CompositeNode) input, augmentPath);
-                        if (value != null && value.getValue() != null) {
-                            ret.put(codec.getKey(), (Augmentation) value.getValue());
-                        }
+            for (AugmentationCodecWrapper codec : codecs) {
+                    // We add Augmentation Identifier to path, in order to
+                    // correctly identify children.
+                    Class type = codec.getDataType();
+                    final InstanceIdentifier augmentPath = path.augmentation(type);
+                    ValueWithQName<?> value = codec.deserialize(input, augmentPath);
+                    if (value != null && value.getValue() != null) {
+                        ret.put(type, (Augmentation) value.getValue());
                     }
-                }
             }
             return ret;
         }
@@ -1063,8 +1127,7 @@ class LazyGeneratedCodecRegistry implements //
          *
          * Adaptation consists of:
          * <ol>
-         * <li> scan of available (valid) augmentations for
-         * current location
+         * <li>scan of available (valid) augmentations for current location
          * <li>lookup for Java classes derived from this augmentations
          * <li>generation of missing codecs
          * <li>updating Augmentation codecs to work with new location
@@ -1084,27 +1147,27 @@ class LazyGeneratedCodecRegistry implements //
                         InstanceIdentifier augPath = augTarget.augmentation(augType);
                         try {
 
-                            org.opendaylight.yangtools.yang.data.api.InstanceIdentifier domPath = getInstanceIdentifierCodec().serialize(augPath);
-                            if(domPath == null) {
-                                LOG.error("Unable to serialize instance identifier for {}",augPath);
+                            org.opendaylight.yangtools.yang.data.api.InstanceIdentifier domPath = getInstanceIdentifierCodec()
+                                    .serialize(augPath);
+                            if (domPath == null) {
+                                LOG.error("Unable to serialize instance identifier for {}", augPath);
                             }
                         } catch (Exception e) {
-                            LOG.error("Unable to serialize instance identifiers for {}",augPath,e);
+                            LOG.error("Unable to serialize instance identifiers for {}", augPath, e);
                         }
 
                     }
                 } else {
-                    // Omits warning for empty augmentations since they are not represented in data
-                    if(!aug.getChildNodes().isEmpty()) {
+                    // Omits warning for empty augmentations since they are not
+                    // represented in data
+                    if (!aug.getChildNodes().isEmpty()) {
                         LOG.warn("Could not find generated type for augmentation {} with children {}", aug,
-                            aug.getChildNodes());
+                                aug.getChildNodes());
                     }
                 }
             }
         }
 
-
-
         private Type getTypeForAugmentation(final AugmentationSchema aug) {
             Optional<AugmentationSchema> currentAug = Optional.of(aug);
             while (currentAug.isPresent()) {
@@ -1116,6 +1179,12 @@ class LazyGeneratedCodecRegistry implements //
             }
             return null;
         }
+
+        @Override
+        public String toString() {
+            return "AugmentableDispatchCodec [augmentable=" + augmentableType + "]";
+        }
+
     }
 
     @SuppressWarnings("rawtypes")
@@ -1167,9 +1236,6 @@ class LazyGeneratedCodecRegistry implements //
         @Override
         @SuppressWarnings("unchecked")
         public ValueWithQName<T> deserialize(final Node<?> input, final InstanceIdentifier<?> bindingIdentifier) {
-            // if (!isAcceptable(bindingIdentifier)) {
-            // return null;
-            // }
             Object rawCodecValue = getDelegate().deserialize(input, bindingIdentifier);
             return new ValueWithQName<T>(input.getNodeType(), (T) rawCodecValue);
         }
@@ -1188,14 +1254,20 @@ class LazyGeneratedCodecRegistry implements //
         }
 
         @Override
-        public boolean isApplicable(final InstanceIdentifier location) {
-            return isAcceptable(location);
+        public boolean isApplicable(final InstanceIdentifier parentPath,final CompositeNode parentData) {
+            return isAcceptable(parentPath);
         }
 
         @Override
         public Class<?> getDataType() {
             return augmentationType;
         }
+
+        @Override
+        public String toString() {
+            return "AugmentationCodecWrapper [augmentation=" + augmentationType
+                    + ", knownLocations=" + validAugmentationTargets.keySet() + "]";
+        }
     }
 
     @SuppressWarnings("rawtypes")