BUG-997 Add SourceIdentifier to MissingSchemaSourceException
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / repo / URLSchemaContextResolver.java
1 /*
2  * Copyright (c) 2014 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.repo;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
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.cache.Cache;
17 import com.google.common.cache.CacheBuilder;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.util.concurrent.CheckedFuture;
20 import com.google.common.util.concurrent.Futures;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.URL;
25 import java.util.Collection;
26 import java.util.concurrent.ConcurrentLinkedDeque;
27 import java.util.concurrent.atomic.AtomicReference;
28
29 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
30 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
31 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
32 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
33 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
34 import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
35 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
36 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
37 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
38 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
39 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
40 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
41 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
42 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
43 import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource;
44 import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 @Beta
49 public class URLSchemaContextResolver implements SchemaSourceProvider<YangTextSchemaSource> {
50     private static final Logger LOG = LoggerFactory.getLogger(URLSchemaContextResolver.class);
51
52     private final Cache<SourceIdentifier, YangTextSchemaSource> sources = CacheBuilder.newBuilder().build();
53     private final Collection<SourceIdentifier> requiredSources = new ConcurrentLinkedDeque<>();
54     private final AtomicReference<Optional<SchemaContext>> currentSchemaContext =
55             new AtomicReference<>(Optional.<SchemaContext>absent());
56     private final SchemaSourceRegistry registry;
57     private final SchemaRepository repository;
58     private volatile Object version = new Object();
59     private volatile Object contextVersion = version;
60
61     private URLSchemaContextResolver(final SchemaRepository repository, final SchemaSourceRegistry registry) {
62         this.repository = Preconditions.checkNotNull(repository);
63         this.registry = Preconditions.checkNotNull(registry);
64     }
65
66     public static URLSchemaContextResolver create(final String name) {
67         final SharedSchemaRepository sharedRepo = new SharedSchemaRepository(name);
68         return new URLSchemaContextResolver(sharedRepo, sharedRepo);
69     }
70
71     /**
72      * Register a URL hosting a YANG Text file.
73      *
74      * @param url URL
75      * @throws YangSyntaxErrorException When the YANG file is syntactically invalid
76      * @throws IOException when the URL is not readable
77      * @throws SchemaSourceException When parsing encounters general error
78      */
79     public URLRegistration registerSource(final URL url) throws SchemaSourceException, IOException, YangSyntaxErrorException {
80         checkArgument(url != null, "Supplied URL must not be null");
81
82         final SourceIdentifier guessedId = new SourceIdentifier(url.getFile(), Optional.<String>absent());
83         final YangTextSchemaSource text = new YangTextSchemaSource(guessedId) {
84             @Override
85             public InputStream openStream() throws IOException {
86                 return url.openStream();
87             }
88
89             @Override
90             protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
91                 return toStringHelper.add("url", url);
92             }
93         };
94
95         final ASTSchemaSource ast = TextToASTTransformer.TRANSFORMATION.apply(text).checkedGet();
96         LOG.trace("Resolved URL {} to source {}", url, ast);
97
98         final SourceIdentifier resolvedId = ast.getIdentifier();
99         final SchemaSourceRegistration<YangTextSchemaSource> reg = registry.registerSchemaSource(this,
100                 PotentialSchemaSource.create(resolvedId, YangTextSchemaSource.class, 0));
101
102         requiredSources.add(resolvedId);
103         LOG.trace("Added source {} to schema context requirements", resolvedId);
104         version = new Object();
105
106         return new AbstractURLRegistration(text) {
107             @Override
108             protected void removeRegistration() {
109                 requiredSources.remove(resolvedId);
110                 LOG.trace("Removed source {} from schema context requirements", resolvedId);
111                 version = new Object();
112                 reg.close();
113                 sources.invalidate(resolvedId);
114             }
115         };
116     }
117
118     /**
119      * Try to parse all currently available yang files and build new schema context.
120      * @return new schema context iif there is at least 1 yang file registered and
121      *         new schema context was successfully built.
122      */
123     public Optional<SchemaContext> getSchemaContext() {
124         final SchemaContextFactory factory = repository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
125         Optional<SchemaContext> sc;
126         Object v;
127         do {
128             // Spin get stable context version
129             Object cv;
130             do {
131                 cv = contextVersion;
132                 sc = currentSchemaContext.get();
133                 if (version == cv) {
134                     return sc;
135                 }
136             } while (cv != contextVersion);
137
138             // Version has been updated
139             Collection<SourceIdentifier> sources;
140             do {
141                 v = version;
142                 sources = ImmutableList.copyOf(requiredSources);
143             } while (v != version);
144
145             while (true) {
146                 final CheckedFuture<SchemaContext, SchemaResolutionException> f = factory.createSchemaContext(sources);
147                 try {
148                     sc = Optional.of(f.checkedGet());
149                     break;
150                 } catch (SchemaResolutionException e) {
151                     LOG.info("Failed to fully assemble schema context for {}", sources, e);
152                     sources = e.getResolvedSources();
153                 }
154             }
155
156             synchronized (this) {
157                 if (contextVersion == cv) {
158                     currentSchemaContext.set(sc);
159                     contextVersion = v;
160                 }
161             }
162         } while (version == v);
163
164         return sc;
165     }
166
167     @Override
168     public CheckedFuture<YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
169         final YangTextSchemaSource ret = sources.getIfPresent(sourceIdentifier);
170         if (ret == null) {
171             return Futures.<YangTextSchemaSource, SchemaSourceException>immediateFailedCheckedFuture(
172                     new MissingSchemaSourceException("URL for " + sourceIdentifier + " not registered", sourceIdentifier));
173         }
174
175         return Futures.immediateCheckedFuture(ret);
176     }
177 }