From ed8e195e6e5343d951a60ca008d5586575f2b8b4 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Fri, 19 Feb 2021 11:20:09 +0100 Subject: [PATCH] Refactor SharedSchemaContextFactory Update SharedSchemaContextFactory design by implementing an explicit cache instead of attempting to use Guava. While this is a bit more code, it solves the problem of initial thundering heard causing concurrent EffectiveModelContext. JIRA: YANGTOOLS-1252 Change-Id: If6e41a8611b650018a1b8961e9c945dfab1f1d88 Signed-off-by: Robert Varga --- .../yang/parser/repo/AssembleSources.java | 108 ++++++++ .../SharedEffectiveModelContextFactory.java | 238 ++++++++++++++++ .../repo/SharedSchemaContextFactory.java | 259 ------------------ .../parser/repo/SharedSchemaRepository.java | 2 +- .../parser/repo/SourceIdMismatchDetector.java | 60 ++++ ...aredEffectiveModelContextFactoryTest.java} | 10 +- 6 files changed, 412 insertions(+), 265 deletions(-) create mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java create mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactory.java delete mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactory.java create mode 100644 yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SourceIdMismatchDetector.java rename yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/{SharedSchemaContextFactoryTest.java => SharedEffectiveModelContextFactoryTest.java} (92%) diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java new file mode 100644 index 0000000000..98754650ee --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 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.yang.parser.repo; + +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.FluentFuture; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.concepts.SemVer; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.parser.api.YangParser; +import org.opendaylight.yangtools.yang.model.parser.api.YangParserException; +import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException; +import org.opendaylight.yangtools.yang.model.repo.api.SemVerSourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; +import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; +import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class AssembleSources implements AsyncFunction, EffectiveModelContext> { + private static final Logger LOG = LoggerFactory.getLogger(AssembleSources.class); + + private final @NonNull Function getIdentifier; + private final @NonNull SchemaContextFactoryConfiguration config; + private final @NonNull YangParserFactory parserFactory; + + AssembleSources(final @NonNull YangParserFactory parserFactory, + final @NonNull SchemaContextFactoryConfiguration config) { + this.parserFactory = parserFactory; + this.config = config; + switch (config.getStatementParserMode()) { + case SEMVER_MODE: + this.getIdentifier = AssembleSources::getSemVerIdentifier; + break; + default: + this.getIdentifier = IRSchemaSource::getIdentifier; + } + } + + @Override + public FluentFuture apply(final List sources) + throws SchemaResolutionException, ReactorException { + final Map srcs = Maps.uniqueIndex(sources, getIdentifier); + final Map deps = + Maps.transformValues(srcs, YangModelDependencyInfo::forIR); + + LOG.debug("Resolving dependency reactor {}", deps); + + final StatementParserMode statementParserMode = config.getStatementParserMode(); + final DependencyResolver res = statementParserMode == StatementParserMode.SEMVER_MODE + ? SemVerDependencyResolver.create(deps) : RevisionDependencyResolver.create(deps); + if (!res.getUnresolvedSources().isEmpty()) { + LOG.debug("Omitting models {} due to unsatisfied imports {}", res.getUnresolvedSources(), + res.getUnsatisfiedImports()); + throw new SchemaResolutionException("Failed to resolve required models", + res.getResolvedSources(), res.getUnsatisfiedImports()); + } + + final YangParser parser = parserFactory.createParser(statementParserMode); + config.getSupportedFeatures().ifPresent(parser::setSupportedFeatures); + config.getModulesDeviatedByModules().ifPresent(parser::setModulesWithSupportedDeviations); + + for (final Entry entry : srcs.entrySet()) { + try { + parser.addSource(entry.getValue()); + } catch (YangSyntaxErrorException | IOException e) { + throw new SchemaResolutionException("Failed to add source " + entry.getKey(), e); + } + } + + final EffectiveModelContext schemaContext; + try { + schemaContext = parser.buildEffectiveModel(); + } catch (final YangParserException e) { + throw new SchemaResolutionException("Failed to resolve required models", e); + } + + return immediateFluentFuture(schemaContext); + } + + private static SemVerSourceIdentifier getSemVerIdentifier(final IRSchemaSource source) { + final SourceIdentifier identifier = source.getIdentifier(); + final SemVer semver = YangModelDependencyInfo.findSemanticVersion(source.getRootStatement(), identifier); + if (identifier instanceof SemVerSourceIdentifier && semver == null) { + return (SemVerSourceIdentifier) identifier; + } + + return SemVerSourceIdentifier.create(identifier.getName(), identifier.getRevision(), semver); + } +} \ No newline at end of file diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactory.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactory.java new file mode 100644 index 0000000000..7c7ff28884 --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactory.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2021 PANTHEON.tech, s.r.o. + * + * 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.yang.parser.repo; + +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An almost-simple cache. EffectiveModel computation is explicitly asynchronous and we are also threadless, i.e. we + * hijack repository threads to do our work. + */ +final class SharedEffectiveModelContextFactory implements EffectiveModelContextFactory { + private static final class CacheEntry { + private static final Function> REF; + private static final VarHandle STATE; + + static { + try { + STATE = MethodHandles.lookup().findVarHandle(CacheEntry.class, "state", Object.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + + String prop = System.getProperty("org.opendaylight.yangtools.yang.parser.repo.shared-refs", "weak"); + switch (prop) { + case "soft": + REF = SoftReference::new; + break; + case "weak": + REF = WeakReference::new; + break; + default: + LOG.warn("Invalid shared-refs \"{}\", defaulting to weak references", prop); + prop = "weak"; + REF = WeakReference::new; + } + LOG.info("Using {} references", prop); + } + + // This field can be in one of two states: + // - SettableFuture, in which case the model is being computed + // - Reference, in which case the model is available through the reference (unless cleared) + @SuppressWarnings("unused") + private volatile Object state = SettableFuture.create(); + + @SuppressWarnings("unchecked") + @Nullable ListenableFuture future() { + final Object local = STATE.getAcquire(this); + if (local instanceof SettableFuture) { + return (SettableFuture) local; + } + verify(local instanceof Reference, "Unexpected state %s", local); + final EffectiveModelContext model = ((Reference) local).get(); + return model == null ? null : Futures.immediateFuture(model); + } + + @SuppressWarnings("unchecked") + @NonNull SettableFuture getFuture() { + final Object local = STATE.getAcquire(this); + verify(local instanceof SettableFuture, "Unexpected state %s", local); + return (SettableFuture) local; + } + + void resolve(final EffectiveModelContext context) { + final SettableFuture future = getFuture(); + // Publish a weak reference before triggering any listeners on the future so that newcomers can see it + final Object witness = STATE.compareAndExchangeRelease(this, future, REF.apply(context)); + verify(witness == future, "Unexpected witness %s", witness); + future.set(context); + } + } + + + private static final Logger LOG = LoggerFactory.getLogger(SharedEffectiveModelContextFactory.class); + private static final Cleaner CLEANER = Cleaner.create(); + + private final ConcurrentMap, CacheEntry> cache = new ConcurrentHashMap<>(); + private final AssembleSources assembleSources; + private final SchemaRepository repository; + + SharedEffectiveModelContextFactory(final @NonNull SharedSchemaRepository repository, + final @NonNull SchemaContextFactoryConfiguration config) { + this.repository = requireNonNull(repository); + this.assembleSources = new AssembleSources(repository.factory(), config); + + } + + @Override + public @NonNull ListenableFuture createEffectiveModelContext( + final @NonNull Collection requiredSources) { + return createEffectiveModel(dedupSources(requiredSources)); + } + + @NonNull ListenableFuture createEffectiveModel(final Set sources) { + final CacheEntry existing = cache.get(sources); + return existing != null ? acquireModel(sources, existing) : computeModel(sources); + } + + // We may have an entry, but we do not know in what state it is in: it may be stable, it may be being built up + // or in process of being retired. + private @NonNull ListenableFuture acquireModel(final Set sources, + final @NonNull CacheEntry entry) { + // Request a future from the entry, which indicates the context is either available or being constructed + final ListenableFuture existing = entry.future(); + if (existing != null) { + return existing; + } + // The entry cannot satisfy our request: remove it and fall back to computation + cache.remove(sources, entry); + return computeModel(sources); + } + + private @NonNull ListenableFuture computeModel(final Set sources) { + // Insert a new entry until we succeed or there is a workable entry + final CacheEntry ourEntry = new CacheEntry(); + while (true) { + final CacheEntry prevEntry = cache.putIfAbsent(sources, ourEntry); + if (prevEntry == null) { + // successful insert + break; + } + + // ... okay, we have raced, but is the entry still usable? + final ListenableFuture existing = prevEntry.future(); + if (existing != null) { + // .. yup, we are done here + return existing; + } + + // ... no dice, remove the entry and retry + cache.remove(sources, prevEntry); + } + + // Acquire the future first, then kick off computation. That way we do not need to worry about races around + // EffectiveModelContext being garbage-collected just after have computed it and before we have acquired a + // reference to it. + final ListenableFuture result = ourEntry.getFuture(); + resolveEntry(sources, ourEntry); + return result; + } + + private void resolveEntry(final Set sources, final CacheEntry entry) { + LOG.debug("Starting assembly of {} sources", sources.size()); + final Stopwatch sw = Stopwatch.createStarted(); + + // Request all sources be loaded + ListenableFuture> sf = Futures.allAsList(Collections2.transform(sources, + identifier -> repository.getSchemaSource(identifier, IRSchemaSource.class))); + + // Detect mismatch between requested Source IDs and IDs that are extracted from parsed source + // Also remove duplicates if present + // We are relying on preserved order of uniqueSourceIdentifiers as well as sf + sf = Futures.transform(sf, new SourceIdMismatchDetector(sources), MoreExecutors.directExecutor()); + + // Assemble sources into a schema context + final ListenableFuture cf = Futures.transformAsync(sf, assembleSources, + MoreExecutors.directExecutor()); + + // FIXME: we do not deal with invalidation here. We should monitor the repository for changes in source schemas + // and react appropriately: + // - in case we failed certainly want to invalidate the entry + // - in case of success ... that's something to consider + Futures.addCallback(cf, new FutureCallback() { + @Override + public void onSuccess(final EffectiveModelContext result) { + LOG.debug("Finished assembly of {} sources in {}", sources.size(), sw); + + // Remove the entry when the context is GC'd + final Stopwatch residence = Stopwatch.createStarted(); + CLEANER.register(result, () -> { + LOG.debug("Removing entry after {}", residence); + cache.remove(sources, entry); + }); + + // Flip the entry to resolved + entry.resolve(result); + } + + @Override + public void onFailure(final Throwable cause) { + LOG.debug("Failed assembly of {} in {}", sources, sw, cause); + entry.getFuture().setException(cause); + } + }, MoreExecutors.directExecutor()); + } + + /** + * Return a set of de-duplicated inputs. + * + * @return set (preserving ordering) from the input collection + */ + private static ImmutableSet dedupSources(final Collection sources) { + final ImmutableSet result = ImmutableSet.copyOf(sources); + if (result.size() != sources.size()) { + LOG.warn("Duplicate sources requested for schema context, removed duplicate sources: {}", + Collections2.filter(result, input -> Iterables.frequency(sources, input) > 1)); + } + return result; + } +} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactory.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactory.java deleted file mode 100644 index e9a4fc6d84..0000000000 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactory.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2014 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.yang.parser.repo; - -import static java.util.Objects.requireNonNull; -import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; - -import com.google.common.base.Function; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import com.google.common.util.concurrent.AsyncFunction; -import com.google.common.util.concurrent.FluentFuture; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import java.io.IOException; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import org.eclipse.jdt.annotation.NonNull; -import org.gaul.modernizer_maven_annotations.SuppressModernizer; -import org.opendaylight.yangtools.concepts.SemVer; -import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; -import org.opendaylight.yangtools.yang.model.parser.api.YangParser; -import org.opendaylight.yangtools.yang.model.parser.api.YangParserException; -import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; -import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; -import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory; -import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration; -import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException; -import org.opendaylight.yangtools.yang.model.repo.api.SemVerSourceIdentifier; -import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; -import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; -import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; -import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -final class SharedSchemaContextFactory implements EffectiveModelContextFactory { - private static final Logger LOG = LoggerFactory.getLogger(SharedSchemaContextFactory.class); - - private final Cache, EffectiveModelContext> revisionCache = CacheBuilder.newBuilder() - .weakValues().build(); - private final Cache, EffectiveModelContext> semVerCache = CacheBuilder.newBuilder() - .weakValues().build(); - private final @NonNull SharedSchemaRepository repository; - private final @NonNull SchemaContextFactoryConfiguration config; - - SharedSchemaContextFactory(final @NonNull SharedSchemaRepository repository, - final @NonNull SchemaContextFactoryConfiguration config) { - this.repository = requireNonNull(repository); - this.config = requireNonNull(config); - } - - @Override - public @NonNull ListenableFuture createEffectiveModelContext( - final @NonNull Collection requiredSources) { - return createSchemaContext(requiredSources, - config.getStatementParserMode() == StatementParserMode.SEMVER_MODE ? semVerCache : revisionCache, - new AssembleSources(repository.factory(), config)); - } - - private @NonNull ListenableFuture createSchemaContext( - final Collection requiredSources, - final Cache, EffectiveModelContext> cache, - final AsyncFunction, EffectiveModelContext> assembleSources) { - // Make sources unique - final List uniqueSourceIdentifiers = deDuplicateSources(requiredSources); - - final EffectiveModelContext existing = cache.getIfPresent(uniqueSourceIdentifiers); - if (existing != null) { - LOG.debug("Returning cached context {}", existing); - return immediateFluentFuture(existing); - } - - // Request all sources be loaded - ListenableFuture> sf = Futures.allAsList(Collections2.transform(uniqueSourceIdentifiers, - this::requestSource)); - - // Detect mismatch between requested Source IDs and IDs that are extracted from parsed source - // Also remove duplicates if present - // We are relying on preserved order of uniqueSourceIdentifiers as well as sf - sf = Futures.transform(sf, new SourceIdMismatchDetector(uniqueSourceIdentifiers), - MoreExecutors.directExecutor()); - - // Assemble sources into a schema context - final ListenableFuture cf = Futures.transformAsync(sf, assembleSources, - MoreExecutors.directExecutor()); - - final SettableFuture rf = SettableFuture.create(); - Futures.addCallback(cf, new FutureCallback() { - @Override - public void onSuccess(final EffectiveModelContext result) { - // Deduplicate concurrent loads - final EffectiveModelContext existing; - try { - existing = cache.get(uniqueSourceIdentifiers, () -> result); - } catch (ExecutionException e) { - LOG.warn("Failed to recheck result with cache, will use computed value", e); - rf.set(result); - return; - } - - rf.set(existing); - } - - @Override - public void onFailure(final Throwable cause) { - LOG.debug("Failed to assemble sources", cause); - rf.setException(cause); - } - }, MoreExecutors.directExecutor()); - - return rf; - } - - private ListenableFuture requestSource(final @NonNull SourceIdentifier identifier) { - return repository.getSchemaSource(identifier, IRSchemaSource.class); - } - - /** - * Return a set of de-duplicated inputs. - * - * @return set (preserving ordering) from the input collection - */ - private static List deDuplicateSources(final Collection requiredSources) { - final Set uniqueSourceIdentifiers = new LinkedHashSet<>(requiredSources); - if (uniqueSourceIdentifiers.size() == requiredSources.size()) { - // Can potentially reuse input - return ImmutableList.copyOf(requiredSources); - } - - LOG.warn("Duplicate sources requested for schema context, removed duplicate sources: {}", - Collections2.filter(uniqueSourceIdentifiers, input -> Iterables.frequency(requiredSources, input) > 1)); - return ImmutableList.copyOf(uniqueSourceIdentifiers); - } - - @SuppressModernizer - private static final class SourceIdMismatchDetector implements Function, - List> { - private final List sourceIdentifiers; - - SourceIdMismatchDetector(final List sourceIdentifiers) { - this.sourceIdentifiers = requireNonNull(sourceIdentifiers); - } - - @Override - public List apply(final List input) { - final Map filtered = new LinkedHashMap<>(); - - for (int i = 0; i < input.size(); i++) { - - final SourceIdentifier expectedSId = sourceIdentifiers.get(i); - final IRSchemaSource irSchemaSource = input.get(i); - final SourceIdentifier realSId = irSchemaSource.getIdentifier(); - - if (!expectedSId.equals(realSId)) { - LOG.warn("Source identifier mismatch for module \"{}\", requested as {} but actually is {}. " - + "Using actual id", expectedSId.getName(), expectedSId, realSId); - } - - if (filtered.containsKey(realSId)) { - LOG.warn("Duplicate source for module {} detected in reactor", realSId); - } - - filtered.put(realSId, irSchemaSource); - - } - return ImmutableList.copyOf(filtered.values()); - } - } - - private static final class AssembleSources implements AsyncFunction, EffectiveModelContext> { - private final @NonNull YangParserFactory parserFactory; - private final @NonNull SchemaContextFactoryConfiguration config; - private final @NonNull Function getIdentifier; - - private AssembleSources(final @NonNull YangParserFactory parserFactory, - final @NonNull SchemaContextFactoryConfiguration config) { - this.parserFactory = parserFactory; - this.config = config; - switch (config.getStatementParserMode()) { - case SEMVER_MODE: - this.getIdentifier = AssembleSources::getSemVerIdentifier; - break; - default: - this.getIdentifier = IRSchemaSource::getIdentifier; - } - } - - @Override - public FluentFuture apply(final List sources) - throws SchemaResolutionException, ReactorException { - final Map srcs = Maps.uniqueIndex(sources, getIdentifier); - final Map deps = - Maps.transformValues(srcs, YangModelDependencyInfo::forIR); - - LOG.debug("Resolving dependency reactor {}", deps); - - final StatementParserMode statementParserMode = config.getStatementParserMode(); - final DependencyResolver res = statementParserMode == StatementParserMode.SEMVER_MODE - ? SemVerDependencyResolver.create(deps) : RevisionDependencyResolver.create(deps); - if (!res.getUnresolvedSources().isEmpty()) { - LOG.debug("Omitting models {} due to unsatisfied imports {}", res.getUnresolvedSources(), - res.getUnsatisfiedImports()); - throw new SchemaResolutionException("Failed to resolve required models", - res.getResolvedSources(), res.getUnsatisfiedImports()); - } - - final YangParser parser = parserFactory.createParser(statementParserMode); - config.getSupportedFeatures().ifPresent(parser::setSupportedFeatures); - config.getModulesDeviatedByModules().ifPresent(parser::setModulesWithSupportedDeviations); - - for (final Entry entry : srcs.entrySet()) { - try { - parser.addSource(entry.getValue()); - } catch (YangSyntaxErrorException | IOException e) { - throw new SchemaResolutionException("Failed to add source " + entry.getKey(), e); - } - } - - final EffectiveModelContext schemaContext; - try { - schemaContext = parser.buildEffectiveModel(); - } catch (final YangParserException e) { - throw new SchemaResolutionException("Failed to resolve required models", e); - } - - return immediateFluentFuture(schemaContext); - } - - private static SemVerSourceIdentifier getSemVerIdentifier(final IRSchemaSource source) { - final SourceIdentifier identifier = source.getIdentifier(); - final SemVer semver = YangModelDependencyInfo.findSemanticVersion(source.getRootStatement(), identifier); - if (identifier instanceof SemVerSourceIdentifier && semver == null) { - return (SemVerSourceIdentifier) identifier; - } - - return SemVerSourceIdentifier.create(identifier.getName(), identifier.getRevision(), semver); - } - } -} diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaRepository.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaRepository.java index ca490683c5..2636991c7b 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaRepository.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaRepository.java @@ -39,7 +39,7 @@ public final class SharedSchemaRepository extends AbstractSchemaRepository imple .build(new CacheLoader() { @Override public EffectiveModelContextFactory load(final SchemaContextFactoryConfiguration key) { - return new SharedSchemaContextFactory(SharedSchemaRepository.this, key); + return new SharedEffectiveModelContextFactory(SharedSchemaRepository.this, key); } }); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SourceIdMismatchDetector.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SourceIdMismatchDetector.java new file mode 100644 index 0000000000..070b18f1ab --- /dev/null +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SourceIdMismatchDetector.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 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.yang.parser.repo; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.gaul.modernizer_maven_annotations.SuppressModernizer; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressModernizer +final class SourceIdMismatchDetector implements Function, List> { + private static final Logger LOG = LoggerFactory.getLogger(SourceIdMismatchDetector.class); + + private final Set sourceIdentifiers; + + SourceIdMismatchDetector(final Set sourceIdentifiers) { + this.sourceIdentifiers = requireNonNull(sourceIdentifiers); + } + + @Override + public List apply(final List input) { + final Iterator srcIt = sourceIdentifiers.iterator(); + final Iterator it = input.iterator(); + + final Map filtered = new LinkedHashMap<>(); + while (it.hasNext()) { + final IRSchemaSource irSchemaSource = it.next(); + final SourceIdentifier realSId = irSchemaSource.getIdentifier(); + if (srcIt.hasNext()) { + final SourceIdentifier expectedSId = srcIt.next(); + if (!expectedSId.equals(realSId)) { + LOG.warn("Source identifier mismatch for module \"{}\", requested as {} but actually is {}. " + + "Using actual id", expectedSId.getName(), expectedSId, realSId); + } + } + + final IRSchemaSource prev = filtered.put(realSId, irSchemaSource); + if (prev != null) { + LOG.warn("Duplicate source for module {} detected in reactor", realSId); + } + } + + return ImmutableList.copyOf(filtered.values()); + } +} \ No newline at end of file diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactoryTest.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactoryTest.java similarity index 92% rename from yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactoryTest.java rename to yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactoryTest.java index a553486b10..6e88c41d85 100644 --- a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactoryTest.java +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedEffectiveModelContextFactoryTest.java @@ -27,7 +27,7 @@ import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToIRTransformer; @RunWith(MockitoJUnitRunner.StrictStubs.class) -public class SharedSchemaContextFactoryTest { +public class SharedEffectiveModelContextFactoryTest { private final SharedSchemaRepository repository = new SharedSchemaRepository("test"); @@ -54,8 +54,8 @@ public class SharedSchemaContextFactoryTest { @Test public void testCreateSchemaContextWithDuplicateRequiredSources() throws InterruptedException, ExecutionException { - final SharedSchemaContextFactory sharedSchemaContextFactory = new SharedSchemaContextFactory(repository, - config); + final SharedEffectiveModelContextFactory sharedSchemaContextFactory = + new SharedEffectiveModelContextFactory(repository, config); final ListenableFuture schemaContext = sharedSchemaContextFactory.createEffectiveModelContext(s1, s1, s2); assertNotNull(schemaContext.get()); @@ -79,8 +79,8 @@ public class SharedSchemaContextFactoryTest { repository.registerSchemaSource(provider, PotentialSchemaSource.create( sIdWithoutRevision, IRSchemaSource.class, PotentialSchemaSource.Costs.IMMEDIATE.getValue())); - final SharedSchemaContextFactory sharedSchemaContextFactory = new SharedSchemaContextFactory(repository, - config); + final SharedEffectiveModelContextFactory sharedSchemaContextFactory = + new SharedEffectiveModelContextFactory(repository, config); final ListenableFuture schemaContext = sharedSchemaContextFactory.createEffectiveModelContext(sIdWithoutRevision, provider.getId()); assertNotNull(schemaContext.get()); -- 2.36.6