BUG-4456: add RecursiveExtensionResolver
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / BuildGlobalContext.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Verify;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.Lists;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Objects;
23 import java.util.Set;
24 import java.util.function.Predicate;
25 import javax.annotation.Nonnull;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
28 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
29 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
30 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
31 import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode;
32 import org.opendaylight.yangtools.yang.parser.spi.meta.DerivedNamespaceBehaviour;
33 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
34 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
35 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableException;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.SomeModifiersUnresolvedException;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
42 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
43 import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
44 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace;
45 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures;
46 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
47 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
48 import org.opendaylight.yangtools.yang.parser.stmt.reactor.SourceSpecificContext.PhaseCompletionProgress;
49 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.RecursiveObjectLeaker;
50 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
51 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBehaviour.Registry {
56     private static final Logger LOG = LoggerFactory.getLogger(BuildGlobalContext.class);
57
58     private static final List<ModelProcessingPhase> PHASE_EXECUTION_ORDER = ImmutableList
59             .<ModelProcessingPhase> builder().add(ModelProcessingPhase.SOURCE_PRE_LINKAGE)
60             .add(ModelProcessingPhase.SOURCE_LINKAGE).add(ModelProcessingPhase.STATEMENT_DEFINITION)
61             .add(ModelProcessingPhase.FULL_DECLARATION).add(ModelProcessingPhase.EFFECTIVE_MODEL).build();
62
63     private final Map<QName, StatementDefinitionContext<?, ?, ?>> definitions = new HashMap<>();
64     private final Map<Class<?>, NamespaceBehaviourWithListeners<?, ?, ?>> supportedNamespaces = new HashMap<>();
65
66     private final Map<ModelProcessingPhase, StatementSupportBundle> supports;
67     private final Set<SourceSpecificContext> sources = new HashSet<>();
68
69     private ModelProcessingPhase currentPhase = ModelProcessingPhase.INIT;
70     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
71
72     private final boolean enabledSemanticVersions;
73
74     public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports,
75             final StatementParserMode statementParserMode, final Predicate<QName> isFeatureSupported) {
76         super();
77         this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
78         Preconditions.checkNotNull(statementParserMode, "Statement parser mode must not be null.");
79         this.enabledSemanticVersions = statementParserMode == StatementParserMode.SEMVER_MODE;
80
81         addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES,
82                 Preconditions.checkNotNull(isFeatureSupported, "Supported feature predicate must not be null."));
83     }
84
85     public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports,
86             final Map<ValidationBundleType, Collection<?>> supportedValidation,
87             final StatementParserMode statementParserMode, final Predicate<QName> isFeatureSupported) {
88         super();
89         this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
90         Preconditions.checkNotNull(statementParserMode, "Statement parser mode must not be null.");
91         this.enabledSemanticVersions = statementParserMode == StatementParserMode.SEMVER_MODE;
92
93         for (Entry<ValidationBundleType, Collection<?>> validationBundle : supportedValidation.entrySet()) {
94             addToNs(ValidationBundlesNamespace.class, validationBundle.getKey(), validationBundle.getValue());
95         }
96
97         addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES,
98                 Preconditions.checkNotNull(isFeatureSupported, "Supported feature predicate must not be null."));
99     }
100
101     public boolean isEnabledSemanticVersioning() {
102         return enabledSemanticVersions;
103     }
104
105     public StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase currentPhase) {
106         return supports.get(currentPhase);
107     }
108
109     public void addSource(@Nonnull final StatementStreamSource source) {
110         sources.add(new SourceSpecificContext(this, source));
111     }
112
113     @Override
114     public StorageNodeType getStorageNodeType() {
115         return StorageNodeType.GLOBAL;
116     }
117
118     @Override
119     public NamespaceStorageNode getParentNamespaceStorage() {
120         return null;
121     }
122
123     @Override
124     public NamespaceBehaviour.Registry getBehaviourRegistry() {
125         return this;
126     }
127
128     @Override
129     public <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getNamespaceBehaviour(
130             final Class<N> type) {
131         NamespaceBehaviourWithListeners<?, ?, ?> potential = supportedNamespaces.get(type);
132         if (potential == null) {
133             NamespaceBehaviour<K, V, N> potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type);
134             if (potentialRaw != null) {
135                 potential = createNamespaceContext(potentialRaw);
136                 supportedNamespaces.put(type, potential);
137             } else {
138                 throw new NamespaceNotAvailableException("Namespace " + type + " is not available in phase "
139                         + currentPhase);
140             }
141         }
142
143         Verify.verify(type.equals(potential.getIdentifier()));
144         /*
145          * Safe cast, previous checkState checks equivalence of key from which
146          * type argument are derived
147          */
148         return (NamespaceBehaviourWithListeners<K, V, N>) potential;
149     }
150
151     @SuppressWarnings({ "unchecked", "rawtypes" })
152     private <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> createNamespaceContext(
153             final NamespaceBehaviour<K, V, N> potentialRaw) {
154         if (potentialRaw instanceof DerivedNamespaceBehaviour) {
155             VirtualNamespaceContext derivedContext = new VirtualNamespaceContext(
156                     (DerivedNamespaceBehaviour) potentialRaw);
157             getNamespaceBehaviour(((DerivedNamespaceBehaviour) potentialRaw).getDerivedFrom()).addDerivedNamespace(
158                     derivedContext);
159             return derivedContext;
160         }
161         return new SimpleNamespaceContext<>(potentialRaw);
162     }
163
164     public StatementDefinitionContext<?, ?, ?> getStatementDefinition(final QName name) {
165         StatementDefinitionContext<?, ?, ?> potential = definitions.get(name);
166         if (potential == null) {
167             StatementSupport<?, ?, ?> potentialRaw = supports.get(currentPhase).getStatementDefinition(name);
168             if (potentialRaw != null) {
169                 potential = new StatementDefinitionContext<>(potentialRaw);
170                 definitions.put(name, potential);
171             }
172         }
173         return potential;
174     }
175
176     public EffectiveModelContext build() throws SourceException, ReactorException {
177         for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
178             startPhase(phase);
179             loadPhaseStatements();
180             completePhaseActions();
181             endPhase(phase);
182         }
183         return transform();
184     }
185
186     private EffectiveModelContext transform() {
187         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
188         List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
189         for (SourceSpecificContext source : sources) {
190             rootStatements.add(source.getRoot().buildDeclared());
191         }
192         return new EffectiveModelContext(rootStatements);
193     }
194
195     public EffectiveSchemaContext buildEffective() throws ReactorException {
196         for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
197             startPhase(phase);
198             loadPhaseStatements();
199             completePhaseActions();
200             endPhase(phase);
201         }
202         return transformEffective();
203     }
204
205     private EffectiveSchemaContext transformEffective() throws ReactorException {
206         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
207         List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
208         List<EffectiveStatement<?, ?>> rootEffectiveStatements = new ArrayList<>(sources.size());
209         SourceIdentifier sourceId = null;
210
211         try {
212             for (SourceSpecificContext source : sources) {
213                 final RootStatementContext<?, ?, ?> root = source.getRoot();
214                 sourceId = Utils.createSourceIdentifier(root);
215                 rootStatements.add(root.buildDeclared());
216                 rootEffectiveStatements.add(root.buildEffective());
217             }
218         } catch (SourceException ex) {
219             throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
220         } finally {
221             RecursiveObjectLeaker.cleanup();
222         }
223
224         return new EffectiveSchemaContext(rootStatements, rootEffectiveStatements);
225     }
226
227     private void startPhase(final ModelProcessingPhase phase) {
228         Preconditions.checkState(Objects.equals(finishedPhase, phase.getPreviousPhase()));
229         for (SourceSpecificContext source : sources) {
230             source.startPhase(phase);
231         }
232         currentPhase = phase;
233     }
234
235     private void loadPhaseStatements() throws ReactorException {
236         Preconditions.checkState(currentPhase != null);
237         for (SourceSpecificContext source : sources) {
238             try {
239                 source.loadStatements();
240             } catch (SourceException ex) {
241                 final SourceIdentifier sourceId = Utils.createSourceIdentifier(source.getRoot());
242                 throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
243             }
244         }
245     }
246
247     private SomeModifiersUnresolvedException addSourceExceptions(final List<SourceSpecificContext> sourcesToProgress) {
248         boolean addedCause = false;
249         SomeModifiersUnresolvedException buildFailure = null;
250         for (SourceSpecificContext failedSource : sourcesToProgress) {
251             final SourceException sourceEx = failedSource.failModifiers(currentPhase);
252
253             // Workaround for broken logging implementations which ignore
254             // suppressed exceptions
255             Throwable cause = sourceEx.getCause() != null ? sourceEx.getCause() : sourceEx;
256             if (LOG.isDebugEnabled()) {
257                 LOG.error("Failed to parse YANG from source {}", failedSource, sourceEx);
258             } else {
259                 LOG.error("Failed to parse YANG from source {}: {}", failedSource, cause.getMessage());
260             }
261
262             final Throwable[] suppressed = sourceEx.getSuppressed();
263             if (suppressed.length > 0) {
264                 LOG.error("{} additional errors reported:", suppressed.length);
265
266                 int i = 1;
267                 for (Throwable t : suppressed) {
268                     // FIXME: this should be configured in the appender, really
269                     if (LOG.isDebugEnabled()) {
270                         LOG.error("Error {}: {}", i, t.getMessage(), t);
271                     } else {
272                         LOG.error("Error {}: {}", i, t.getMessage());
273                     }
274
275                     i++;
276                 }
277             }
278
279             if (!addedCause) {
280                 addedCause = true;
281                 final SourceIdentifier sourceId = Utils.createSourceIdentifier(failedSource.getRoot());
282                 buildFailure = new SomeModifiersUnresolvedException(currentPhase, sourceId, sourceEx);
283             } else {
284                 buildFailure.addSuppressed(sourceEx);
285             }
286         }
287         return buildFailure;
288     }
289
290     private void completePhaseActions() throws ReactorException {
291         Preconditions.checkState(currentPhase != null);
292         List<SourceSpecificContext> sourcesToProgress = Lists.newArrayList(sources);
293         SourceIdentifier sourceId = null;
294         try {
295             boolean progressing = true;
296             while (progressing) {
297                 // We reset progressing to false.
298                 progressing = false;
299                 Iterator<SourceSpecificContext> currentSource = sourcesToProgress.iterator();
300                 while (currentSource.hasNext()) {
301                     SourceSpecificContext nextSourceCtx = currentSource.next();
302                     sourceId = Utils.createSourceIdentifier(nextSourceCtx.getRoot());
303                     PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase);
304                     switch (sourceProgress) {
305                     case FINISHED:
306                         currentSource.remove();
307                         // Fallback to progress, since we were able to make
308                         // progress in computation
309                     case PROGRESS:
310                         progressing = true;
311                         break;
312                     case NO_PROGRESS:
313                         // Noop
314                         break;
315                     default:
316                         throw new IllegalStateException("Unsupported phase progress " + sourceProgress);
317                     }
318                 }
319             }
320         } catch (SourceException e) {
321             throw new SomeModifiersUnresolvedException(currentPhase, sourceId, e);
322         }
323         if (!sourcesToProgress.isEmpty()) {
324             final SomeModifiersUnresolvedException buildFailure = addSourceExceptions(sourcesToProgress);
325             throw buildFailure;
326         }
327     }
328
329     private void endPhase(final ModelProcessingPhase phase) {
330         Preconditions.checkState(currentPhase == phase);
331         finishedPhase = currentPhase;
332     }
333
334     public Set<SourceSpecificContext> getSources() {
335         return sources;
336     }
337 }