2 * Copyright (c) 2014 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.yangtools.yang.parser.repo;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import com.google.common.base.Function;
13 import com.google.common.base.Objects.ToStringHelper;
14 import com.google.common.base.Optional;
15 import com.google.common.base.Preconditions;
16 import com.google.common.collect.ArrayListMultimap;
17 import com.google.common.collect.ImmutableMultimap;
18 import com.google.common.collect.Maps;
19 import com.google.common.collect.Maps.EntryTransformer;
20 import com.google.common.collect.Multimap;
21 import com.google.common.collect.Multimaps;
22 import com.google.common.util.concurrent.CheckedFuture;
23 import com.google.common.util.concurrent.FutureCallback;
24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListeningExecutorService;
26 import com.google.common.util.concurrent.MoreExecutors;
27 import com.google.common.util.concurrent.ThreadFactoryBuilder;
29 import java.io.IOException;
30 import java.io.InputStream;
32 import java.util.Collection;
33 import java.util.LinkedHashMap;
35 import java.util.Map.Entry;
36 import java.util.Queue;
37 import java.util.concurrent.ConcurrentLinkedQueue;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.ThreadFactory;
40 import java.util.concurrent.atomic.AtomicReference;
42 import javax.annotation.concurrent.GuardedBy;
43 import javax.annotation.concurrent.ThreadSafe;
45 import org.antlr.v4.runtime.ParserRuleContext;
46 import org.antlr.v4.runtime.tree.ParseTreeWalker;
47 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
48 import org.opendaylight.yangtools.concepts.ObjectRegistration;
49 import org.opendaylight.yangtools.yang.model.api.Module;
50 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
51 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
52 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
53 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException;
54 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
55 import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
56 import org.opendaylight.yangtools.yang.parser.impl.YangParserListenerImpl;
57 import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo;
58 import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource;
59 import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 public class URLSchemaContextResolver {
65 private static final Logger LOG = LoggerFactory.getLogger(URLSchemaContextResolver.class);
66 private static final Function<ASTSchemaSource, YangModelDependencyInfo> EXTRACT_DEPINFO = new Function<ASTSchemaSource, YangModelDependencyInfo>() {
68 public YangModelDependencyInfo apply(final ASTSchemaSource input) {
69 return input.getDependencyInformation();
72 private static final EntryTransformer<SourceIdentifier, Collection<YangModelDependencyInfo>, YangModelDependencyInfo> SQUASH_DEPINFO =
73 new EntryTransformer<SourceIdentifier, Collection<YangModelDependencyInfo>, YangModelDependencyInfo>() {
75 public YangModelDependencyInfo transformEntry(final SourceIdentifier key, final Collection<YangModelDependencyInfo> value) {
76 // FIXME: validate that all the info objects are the same
77 return value.iterator().next();
80 private static final Function<ASTSchemaSource, ParserRuleContext> EXTRACT_AST = new Function<ASTSchemaSource, ParserRuleContext>() {
82 public ParserRuleContext apply(final ASTSchemaSource input) {
83 return input.getAST();
86 private static final EntryTransformer<SourceIdentifier, Collection<ParserRuleContext>, ParserRuleContext> SQUASH_AST =
87 new EntryTransformer<SourceIdentifier, Collection<ParserRuleContext>, ParserRuleContext>() {
89 public ParserRuleContext transformEntry(final SourceIdentifier key, final Collection<ParserRuleContext> value) {
90 // FIXME: validate that all the info objects are the same
91 return value.iterator().next();
96 private final Multimap<SourceIdentifier, ASTSchemaSource> resolvedRegs = ArrayListMultimap.create();
97 private final AtomicReference<Optional<SchemaContext>> currentSchemaContext = new AtomicReference<>(Optional.<SchemaContext>absent());
98 private final Queue<URLRegistration> outstandingRegs = new ConcurrentLinkedQueue<>();
99 private final TextToASTTransformer transformer;
101 private Object version = new Object();
103 private Object contextVersion = version;
105 private final class URLRegistration extends AbstractObjectRegistration<URL> {
107 private CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> future;
109 private ASTSchemaSource result;
111 protected URLRegistration(final URL url, final CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> future) {
113 this.future = Preconditions.checkNotNull(future);
116 private synchronized boolean setResult(final ASTSchemaSource result) {
117 if (future != null) {
118 this.result = result;
126 protected void removeRegistration() {
127 // Cancel the future, but it may already be completing
128 future.cancel(false);
130 synchronized (this) {
132 outstandingRegs.remove(this);
133 if (result != null) {
134 removeSchemaSource(result);
140 private URLSchemaContextResolver(final TextToASTTransformer transformer) {
141 this.transformer = Preconditions.checkNotNull(transformer);
144 public static URLSchemaContextResolver create(final String name) {
145 final ThreadFactory f = new ThreadFactoryBuilder().setDaemon(true).setNameFormat(name + "yangparser-%d").build();
146 final ListeningExecutorService s = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(f));
148 return new URLSchemaContextResolver(TextToASTTransformer.create(s));
152 * Register a URL hosting a YANG Text file.
156 public ObjectRegistration<URL> registerSource(final URL url) {
157 checkArgument(url != null, "Supplied URL must not be null");
159 final SourceIdentifier id = SourceIdentifier.create(url.getFile().toString(), Optional.<String>absent());
160 final YangTextSchemaSource text = new YangTextSchemaSource(id) {
162 public InputStream openStream() throws IOException {
163 return url.openStream();
167 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
168 return toStringHelper.add("url", url);
172 final CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> ast = transformer.transformSchemaSource(text);
173 final URLRegistration reg = new URLRegistration(url, ast);
174 outstandingRegs.add(reg);
176 Futures.addCallback(ast, new FutureCallback<ASTSchemaSource>() {
178 public void onSuccess(final ASTSchemaSource result) {
179 LOG.trace("Resolved URL {} to source {}", url, result);
181 outstandingRegs.remove(reg);
182 if (reg.setResult(result)) {
183 addSchemaSource(result);
188 public void onFailure(final Throwable t) {
189 LOG.warn("Failed to parse YANG text from {}, ignoring it", url, t);
190 outstandingRegs.remove(reg);
197 private synchronized void addSchemaSource(final ASTSchemaSource src) {
198 resolvedRegs.put(src.getIdentifier(), src);
199 version = new Object();
202 private synchronized void removeSchemaSource(final ASTSchemaSource src) {
203 resolvedRegs.put(src.getIdentifier(), src);
204 version = new Object();
208 * Try to parse all currently available yang files and build new schema context.
209 * @return new schema context iif there is at least 1 yang file registered and
210 * new schema context was successfully built.
212 public Optional<SchemaContext> getSchemaContext() {
214 Optional<SchemaContext> result;
215 final Multimap<SourceIdentifier, ASTSchemaSource> sources;
217 synchronized (this) {
218 result = currentSchemaContext.get();
219 if (version == contextVersion) {
223 sources = ImmutableMultimap.copyOf(resolvedRegs);
227 if (!sources.isEmpty()) {
228 final Map<SourceIdentifier, YangModelDependencyInfo> deps =
229 Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_DEPINFO).asMap(), SQUASH_DEPINFO);
231 LOG.debug("Resolving dependency reactor {}", deps);
232 final DependencyResolver res = DependencyResolver.create(deps);
233 if (!res.getUnresolvedSources().isEmpty()) {
234 LOG.debug("Omitting models {} due to unsatisfied imports {}", res.getUnresolvedSources(), res.getUnsatisfiedImports());
237 final Map<SourceIdentifier, ParserRuleContext> asts =
238 Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_AST).asMap(), SQUASH_AST);
240 final ParseTreeWalker walker = new ParseTreeWalker();
241 final Map<SourceIdentifier, ModuleBuilder> sourceToBuilder = new LinkedHashMap<>();
243 for (Entry<SourceIdentifier, ParserRuleContext> entry : asts.entrySet()) {
244 final YangParserListenerImpl yangModelParser = new YangParserListenerImpl(entry.getKey().getName());
245 walker.walk(yangModelParser, entry.getValue());
246 ModuleBuilder moduleBuilder = yangModelParser.getModuleBuilder();
248 moduleBuilder.setSource(sources.get(entry.getKey()).iterator().next().getYangText());
249 sourceToBuilder.put(entry.getKey(), moduleBuilder);
251 LOG.debug("Modules ready for integration");
253 final YangParserImpl parser = YangParserImpl.getInstance();
254 final Collection<Module> modules = parser.buildModules(sourceToBuilder.values());
255 LOG.debug("Integrated cross-references modules");
257 result = Optional.of(parser.assembleContext(modules));
259 result = Optional.absent();
262 synchronized (this) {
264 currentSchemaContext.set(result);
265 contextVersion = version;
269 LOG.debug("Context version {} expected {}, retry", version, v);