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 com.google.common.annotations.Beta;
11 import com.google.common.base.MoreObjects;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Lists;
16 import com.google.common.util.concurrent.CheckedFuture;
17 import com.google.common.util.concurrent.Futures;
19 import java.io.FileInputStream;
20 import java.io.FilenameFilter;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.file.FileVisitResult;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.SimpleFileVisitor;
27 import java.nio.file.StandardCopyOption;
28 import java.nio.file.attribute.BasicFileAttributes;
29 import java.text.DateFormat;
30 import java.text.ParseException;
31 import java.text.SimpleDateFormat;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.List;
36 import java.util.TreeMap;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
40 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
41 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
42 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
43 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
44 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
45 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Cache implementation that stores schemas in form of files under provided folder
52 public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentation> extends AbstractSchemaSourceCache<T> {
54 private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
56 // Init storage adapters
57 private static final Map<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> storageAdapters =
58 Collections.<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> singletonMap(
59 YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());
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 = Preconditions.checkNotNull(storageDirectory);
70 checkSupportedRepresentation(representation);
72 if(!storageDirectory.exists()) {
73 Preconditions.checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
75 Preconditions.checkArgument(storageDirectory.exists());
76 Preconditions.checkArgument(storageDirectory.isDirectory());
77 Preconditions.checkArgument(storageDirectory.canWrite());
78 Preconditions.checkArgument(storageDirectory.canRead());
83 private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
84 for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : storageAdapters.keySet()) {
85 if(supportedRepresentation.isAssignableFrom(representation)) {
90 throw new IllegalArgumentException(String.format(
91 "This cache does not support representation: %s, supported representations are: %s", representation, storageAdapters.keySet()));
94 private static final Pattern CACHED_FILE_PATTERN =
96 "(?<moduleName>[^@]+)" +
97 "(@(?<revision>" + SourceIdentifier.REVISION_PATTERN + "))?");
100 * Restore cache state
102 private void init() {
104 final CachedModulesFileVisitor fileVisitor = new CachedModulesFileVisitor();
106 Files.walkFileTree(storageDirectory.toPath(), fileVisitor);
107 } catch (final IOException e) {
108 LOG.warn("Unable to restore cache from {}. Starting with empty cache", storageDirectory);
112 for (final SourceIdentifier cachedSchema : fileVisitor.getCachedSchemas()) {
113 register(cachedSchema);
118 public synchronized CheckedFuture<? extends T, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
119 final File file = sourceIdToFile(sourceIdentifier, storageDirectory);
120 if(file.exists() && file.canRead()) {
121 LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
122 final SchemaSourceRepresentation restored = storageAdapters.get(representation).restore(sourceIdentifier, file);
123 return Futures.immediateCheckedFuture(representation.cast(restored));
126 LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
127 return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
131 protected synchronized void offer(final T source) {
132 LOG.trace("Source {} offered to cache", source.getIdentifier());
133 final File file = sourceIdToFile(source);
135 LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
139 storeSource(file, source);
140 register(source.getIdentifier());
141 LOG.trace("Source {} stored in cache as {}", source.getIdentifier(), file);
144 private File sourceIdToFile(final T source) {
145 return sourceIdToFile(source.getIdentifier(), storageDirectory);
148 * FIXME: Move of code from deprecated FilesystemSchemaCachingProvider
149 * to reduce cycle. Decrease visibility once FilesystemSchemaCachingProvider
153 public static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
154 final String rev = identifier.getRevision();
156 if (Strings.isNullOrEmpty(rev)) {
157 file = findFileWithNewestRev(identifier, storageDirectory);
159 file = new File(storageDirectory, identifier.toYangFilename());
164 private static File findFileWithNewestRev(final SourceIdentifier identifier, final File storageDirectory) {
165 File[] files = storageDirectory.listFiles(new FilenameFilter() {
166 final Pattern p = Pattern.compile(Pattern.quote(identifier.getName()) + "(\\.yang|@\\d\\d\\d\\d-\\d\\d-\\d\\d.yang)");
169 public boolean accept(final File dir, final String name) {
170 return p.matcher(name).matches();
174 if (files.length == 0) {
175 return new File(storageDirectory, identifier.toYangFilename());
177 if (files.length == 1) {
182 TreeMap<Date, File> map = new TreeMap<>();
183 for (File sorted : files) {
184 String fileName = sorted.getName();
185 Matcher m = SourceIdentifier.REVISION_PATTERN.matcher(fileName);
187 String revStr = m.group();
189 * FIXME: Consider using string for comparison.
190 * String is comparable, pattern check tested format
191 * so comparing as ASCII string should be sufficient
193 DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
195 Date d = df.parse(revStr);
197 } catch (ParseException e) {
198 LOG.info("Unable to parse date from yang file name");
199 map.put(new Date(0L), sorted);
203 map.put(new Date(0L), sorted);
206 file = map.lastEntry().getValue();
211 private void storeSource(final File file, final T schemaRepresentation) {
212 storageAdapters.get(representation).store(file, schemaRepresentation);
215 private static abstract class StorageAdapter<T extends SchemaSourceRepresentation> {
217 private final Class<T> supportedType;
219 protected StorageAdapter(final Class<T> supportedType) {
220 this.supportedType = supportedType;
223 void store(final File file, final SchemaSourceRepresentation schemaSourceRepresentation) {
224 Preconditions.checkArgument(supportedType.isAssignableFrom(schemaSourceRepresentation.getClass()),
225 "Cannot store schema source %s, this adapter only supports %s", schemaSourceRepresentation, supportedType);
227 storeAsType(file, supportedType.cast(schemaSourceRepresentation));
231 protected abstract void storeAsType(final File file, final T cast);
233 public T restore(final SourceIdentifier sourceIdentifier, final File cachedSource) {
234 Preconditions.checkArgument(cachedSource.isFile());
235 Preconditions.checkArgument(cachedSource.exists());
236 Preconditions.checkArgument(cachedSource.canRead());
237 return restoreAsType(sourceIdentifier, cachedSource);
240 protected abstract T restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource);
243 private static final class YangTextSchemaStorageAdapter extends StorageAdapter<YangTextSchemaSource> {
245 protected YangTextSchemaStorageAdapter() {
246 super(YangTextSchemaSource.class);
250 protected void storeAsType(final File file, final YangTextSchemaSource cast) {
252 Files.copy(cast.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
253 } catch (final IOException e) {
254 throw new IllegalStateException("Cannot store schema source " + cast.getIdentifier() + " to " + file, e);
259 public YangTextSchemaSource restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource) {
260 return new YangTextSchemaSource(sourceIdentifier) {
263 protected MoreObjects.ToStringHelper addToStringAttributes(final MoreObjects.ToStringHelper toStringHelper) {
264 return toStringHelper;
268 public InputStream openStream() throws IOException {
269 return new FileInputStream(cachedSource);
275 private static final class CachedModulesFileVisitor extends SimpleFileVisitor<Path> {
276 private final List<SourceIdentifier> cachedSchemas = Lists.newArrayList();
279 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
280 final FileVisitResult fileVisitResult = super.visitFile(file, attrs);
281 String fileName = file.toFile().getName();
282 fileName = com.google.common.io.Files.getNameWithoutExtension(fileName);
284 final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
286 LOG.trace("Restoring cached file {} as {}", file, si.get());
287 cachedSchemas.add(si.get());
289 LOG.debug("Skipping cached file {}, cannot restore source identifier from filename: {}, does not match {}", file, fileName, CACHED_FILE_PATTERN);
291 return fileVisitResult;
294 private static Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
295 final Matcher matcher = CACHED_FILE_PATTERN.matcher(fileName);
296 if (matcher.matches()) {
297 final String moduleName = matcher.group("moduleName");
298 final String revision = matcher.group("revision");
299 return Optional.of(new SourceIdentifier(moduleName, Optional.fromNullable(revision)));
301 return Optional.absent();
305 public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
306 LOG.warn("Unable to restore cached file {}. Ignoring", file, exc);
307 return FileVisitResult.CONTINUE;
310 public List<SourceIdentifier> getCachedSchemas() {
311 return cachedSchemas;