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.model.repo.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.collect.Lists;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.ListenableFuture;
18 import java.io.FilenameFilter;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.nio.file.FileVisitResult;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.SimpleFileVisitor;
25 import java.nio.file.StandardCopyOption;
26 import java.nio.file.attribute.BasicFileAttributes;
27 import java.util.Collections;
28 import java.util.List;
30 import java.util.Optional;
31 import java.util.TreeMap;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 import org.opendaylight.yangtools.yang.common.Revision;
35 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
36 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
37 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
38 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
39 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
40 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
41 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Cache implementation that stores schemas in form of files under provided folder.
48 public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentation>
49 extends AbstractSchemaSourceCache<T> {
51 private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
53 // Init storage adapters
54 private static final Map<Class<? extends SchemaSourceRepresentation>,
55 StorageAdapter<? extends SchemaSourceRepresentation>> STORAGE_ADAPTERS = Collections.singletonMap(
56 YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());
58 private static final Pattern CACHED_FILE_PATTERN =
59 Pattern.compile("(?<moduleName>[^@]+)" + "(@(?<revision>" + Revision.STRING_FORMAT_PATTERN + "))?");
61 private final Class<T> representation;
62 private final File storageDirectory;
64 public FilesystemSchemaSourceCache(
65 final SchemaSourceRegistry consumer, final Class<T> representation, final File storageDirectory) {
66 super(consumer, representation, Costs.LOCAL_IO);
67 this.representation = representation;
68 this.storageDirectory = requireNonNull(storageDirectory);
70 checkSupportedRepresentation(representation);
72 if (!storageDirectory.exists()) {
73 checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
75 checkArgument(storageDirectory.exists());
76 checkArgument(storageDirectory.isDirectory());
77 checkArgument(storageDirectory.canWrite());
78 checkArgument(storageDirectory.canRead());
83 private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
84 for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : STORAGE_ADAPTERS.keySet()) {
85 if (supportedRepresentation.isAssignableFrom(representation)) {
90 throw new IllegalArgumentException(String.format(
91 "This cache does not support representation: %s, supported representations are: %s",
92 representation, STORAGE_ADAPTERS.keySet()));
96 * Restore cache state.
100 final CachedModulesFileVisitor fileVisitor = new CachedModulesFileVisitor();
102 Files.walkFileTree(storageDirectory.toPath(), fileVisitor);
103 } catch (final IOException e) {
104 LOG.warn("Unable to restore cache from {}. Starting with empty cache", storageDirectory);
108 for (final SourceIdentifier cachedSchema : fileVisitor.getCachedSchemas()) {
109 register(cachedSchema);
114 public synchronized ListenableFuture<? extends T> getSource(
115 final SourceIdentifier sourceIdentifier) {
116 final File file = sourceIdToFile(sourceIdentifier, storageDirectory);
117 if (file.exists() && file.canRead()) {
118 LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
119 final SchemaSourceRepresentation restored = STORAGE_ADAPTERS.get(representation).restore(sourceIdentifier,
121 return Futures.immediateFuture(representation.cast(restored));
124 LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
125 return Futures.immediateFailedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
129 protected synchronized void offer(final T source) {
130 LOG.trace("Source {} offered to cache", source.getIdentifier());
131 final File file = sourceIdToFile(source);
133 LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
137 storeSource(file, source);
138 register(source.getIdentifier());
139 LOG.trace("Source {} stored in cache as {}", source.getIdentifier(), file);
142 private File sourceIdToFile(final T source) {
143 return sourceIdToFile(source.getIdentifier(), storageDirectory);
146 static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
147 final Optional<Revision> rev = identifier.getRevision();
149 if (!rev.isPresent()) {
150 // FIXME: this does not look right
151 file = findFileWithNewestRev(identifier, storageDirectory);
153 file = new File(storageDirectory, identifier.toYangFilename());
158 private static File findFileWithNewestRev(final SourceIdentifier identifier, final File storageDirectory) {
159 File[] files = storageDirectory.listFiles(new FilenameFilter() {
160 final Pattern pat = Pattern.compile(Pattern.quote(identifier.getName())
161 + "(\\.yang|@\\d\\d\\d\\d-\\d\\d-\\d\\d.yang)");
164 public boolean accept(final File dir, final String name) {
165 return pat.matcher(name).matches();
169 if (files.length == 0) {
170 return new File(storageDirectory, identifier.toYangFilename());
172 if (files.length == 1) {
177 TreeMap<Optional<Revision>, File> map = new TreeMap<>(Revision::compare);
178 for (File sorted : files) {
179 String fileName = sorted.getName();
180 Matcher match = Revision.STRING_FORMAT_PATTERN.matcher(fileName);
182 String revStr = match.group();
185 rev = Revision.valueOf(revStr);
186 } catch (final IllegalArgumentException e) {
187 LOG.info("Unable to parse date from yang file name {}, falling back to not-present", fileName, e);
191 map.put(Optional.ofNullable(rev), sorted);
194 map.put(Optional.empty(), sorted);
197 file = map.lastEntry().getValue();
202 private void storeSource(final File file, final T schemaRepresentation) {
203 STORAGE_ADAPTERS.get(representation).store(file, schemaRepresentation);
206 private abstract static class StorageAdapter<T extends SchemaSourceRepresentation> {
208 private final Class<T> supportedType;
210 protected StorageAdapter(final Class<T> supportedType) {
211 this.supportedType = supportedType;
214 void store(final File file, final SchemaSourceRepresentation schemaSourceRepresentation) {
215 checkArgument(supportedType.isAssignableFrom(schemaSourceRepresentation.getClass()),
216 "Cannot store schema source %s, this adapter only supports %s", schemaSourceRepresentation,
219 storeAsType(file, supportedType.cast(schemaSourceRepresentation));
222 protected abstract void storeAsType(File file, T cast);
224 public T restore(final SourceIdentifier sourceIdentifier, final File cachedSource) {
225 checkArgument(cachedSource.isFile());
226 checkArgument(cachedSource.exists());
227 checkArgument(cachedSource.canRead());
228 return restoreAsType(sourceIdentifier, cachedSource);
231 protected abstract T restoreAsType(SourceIdentifier sourceIdentifier, File cachedSource);
234 private static final class YangTextSchemaStorageAdapter extends StorageAdapter<YangTextSchemaSource> {
236 protected YangTextSchemaStorageAdapter() {
237 super(YangTextSchemaSource.class);
241 protected void storeAsType(final File file, final YangTextSchemaSource cast) {
242 try (InputStream castStream = cast.openStream()) {
243 Files.copy(castStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
244 } catch (final IOException e) {
245 throw new IllegalStateException("Cannot store schema source " + cast.getIdentifier() + " to " + file,
251 public YangTextSchemaSource restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource) {
252 return new YangTextSchemaSource(sourceIdentifier) {
255 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
256 return toStringHelper;
260 public InputStream openStream() throws IOException {
261 return Files.newInputStream(cachedSource.toPath());
267 private static final class CachedModulesFileVisitor extends SimpleFileVisitor<Path> {
268 private final List<SourceIdentifier> cachedSchemas = Lists.newArrayList();
271 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
272 final FileVisitResult fileVisitResult = super.visitFile(file, attrs);
273 String fileName = file.toFile().getName();
274 fileName = com.google.common.io.Files.getNameWithoutExtension(fileName);
276 final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
277 if (si.isPresent()) {
278 LOG.trace("Restoring cached file {} as {}", file, si.get());
279 cachedSchemas.add(si.get());
281 LOG.debug("Skipping cached file {}, cannot restore source identifier from filename: {},"
282 + " does not match {}", file, fileName, CACHED_FILE_PATTERN);
284 return fileVisitResult;
287 private static Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
288 final Matcher matcher = CACHED_FILE_PATTERN.matcher(fileName);
289 if (matcher.matches()) {
290 final String moduleName = matcher.group("moduleName");
291 final String revision = matcher.group("revision");
292 return Optional.of(RevisionSourceIdentifier.create(moduleName, revision == null ? Optional.empty()
293 : Optional.of(Revision.valueOf(revision))));
295 return Optional.empty();
299 public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
300 LOG.warn("Unable to restore cached file {}. Ignoring", file, exc);
301 return FileVisitResult.CONTINUE;
304 public List<SourceIdentifier> getCachedSchemas() {
305 return cachedSchemas;