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.base.Objects;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.Lists;
14 import com.google.common.util.concurrent.CheckedFuture;
15 import com.google.common.util.concurrent.Futures;
17 import java.io.FileInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.nio.file.FileVisitResult;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.nio.file.SimpleFileVisitor;
24 import java.nio.file.StandardCopyOption;
25 import java.nio.file.attribute.BasicFileAttributes;
26 import java.util.Collections;
27 import java.util.List;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
32 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
33 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
34 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
35 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
36 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
37 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
38 import org.opendaylight.yangtools.yang.model.util.repo.FilesystemSchemaCachingProvider;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * Cache implementation that stores schemas in form of files under provided folder
45 public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentation> extends AbstractSchemaSourceCache<T> {
47 private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
49 // Init storage adapters
50 private static final Map<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> storageAdapters =
51 Collections.<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> singletonMap(
52 YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());
54 private final Class<T> representation;
55 private final File storageDirectory;
57 public FilesystemSchemaSourceCache(
58 final SchemaSourceRegistry consumer, final Class<T> representation, final File storageDirectory) {
59 super(consumer, representation, Costs.LOCAL_IO);
60 this.representation = representation;
61 this.storageDirectory = Preconditions.checkNotNull(storageDirectory);
63 checkSupportedRepresentation(representation);
65 if(storageDirectory.exists() == false) {
66 Preconditions.checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
68 Preconditions.checkArgument(storageDirectory.exists());
69 Preconditions.checkArgument(storageDirectory.isDirectory());
70 Preconditions.checkArgument(storageDirectory.canWrite());
71 Preconditions.checkArgument(storageDirectory.canRead());
76 private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
77 for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : storageAdapters.keySet()) {
78 if(supportedRepresentation.isAssignableFrom(representation)) {
83 throw new IllegalArgumentException(String.format(
84 "This cache does not support representation: %s, supported representations are: %s", representation, storageAdapters.keySet()));
87 private static final Pattern CACHED_FILE_PATTERN =
89 "(?<moduleName>[^@]+)" +
90 "(@(?<revision>" + FilesystemSchemaCachingProvider.REVISION_PATTERN + "))?");
97 final CachedModulesFileVisitor fileVisitor = new CachedModulesFileVisitor();
99 Files.walkFileTree(storageDirectory.toPath(), fileVisitor);
100 } catch (final IOException e) {
101 LOG.warn("Unable to restore cache from {}. Starting with empty cache", storageDirectory);
105 for (final SourceIdentifier cachedSchema : fileVisitor.getCachedSchemas()) {
106 register(cachedSchema);
111 public synchronized CheckedFuture<? extends T, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
112 final File file = FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(sourceIdentifier), storageDirectory);
113 if(file.exists() && file.canRead()) {
114 LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
115 final SchemaSourceRepresentation restored = storageAdapters.get(representation).restore(sourceIdentifier, file);
116 return Futures.immediateCheckedFuture(representation.cast(restored));
119 LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
120 return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
124 protected synchronized void offer(final T source) {
125 LOG.trace("Source {} offered to cache", source.getIdentifier());
126 final File file = sourceIdToFile(source);
128 LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
132 storeSource(file, source);
133 register(source.getIdentifier());
134 LOG.trace("Source {} stored in cache as {}", source.getIdentifier(), file);
137 private File sourceIdToFile(final T source) {
138 return FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(source.getIdentifier()), storageDirectory);
141 private void storeSource(final File file, final T schemaRepresentation) {
142 storageAdapters.get(representation).store(file, schemaRepresentation);
145 private static org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier toLegacy(final SourceIdentifier identifier) {
146 return new org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier(identifier.getName(), Optional.fromNullable(identifier.getRevision()));
149 private static abstract class StorageAdapter<T extends SchemaSourceRepresentation> {
151 private final Class<T> supportedType;
153 protected StorageAdapter(final Class<T> supportedType) {
154 this.supportedType = supportedType;
157 void store(final File file, final SchemaSourceRepresentation schemaSourceRepresentation) {
158 Preconditions.checkArgument(supportedType.isAssignableFrom(schemaSourceRepresentation.getClass()),
159 "Cannot store schema source %s, this adapter only supports %s", schemaSourceRepresentation, supportedType);
161 storeAsType(file, supportedType.cast(schemaSourceRepresentation));
165 protected abstract void storeAsType(final File file, final T cast);
167 public Class<T> getSupportedType() {
168 return supportedType;
171 public T restore(final SourceIdentifier sourceIdentifier, final File cachedSource) {
172 Preconditions.checkArgument(cachedSource.isFile());
173 Preconditions.checkArgument(cachedSource.exists());
174 Preconditions.checkArgument(cachedSource.canRead());
175 return restoreAsType(sourceIdentifier, cachedSource);
178 protected abstract T restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource);
181 private static final class YangTextSchemaStorageAdapter extends StorageAdapter<YangTextSchemaSource> {
183 protected YangTextSchemaStorageAdapter() {
184 super(YangTextSchemaSource.class);
188 protected void storeAsType(final File file, final YangTextSchemaSource cast) {
190 Files.copy(cast.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
191 } catch (final IOException e) {
192 throw new IllegalStateException("Cannot store schema source " + cast.getIdentifier() + " to " + file, e);
197 public YangTextSchemaSource restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource) {
198 return new YangTextSchemaSource(sourceIdentifier) {
201 protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
202 return toStringHelper;
206 public InputStream openStream() throws IOException {
207 return new FileInputStream(cachedSource);
213 private static final class CachedModulesFileVisitor extends SimpleFileVisitor<Path> {
214 private final List<SourceIdentifier> cachedSchemas = Lists.newArrayList();
217 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
218 final FileVisitResult fileVisitResult = super.visitFile(file, attrs);
219 String fileName = file.toFile().getName();
220 fileName = com.google.common.io.Files.getNameWithoutExtension(fileName);
222 final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
224 LOG.trace("Restoring cached file {} as {}", file, si.get());
225 cachedSchemas.add(si.get());
227 LOG.debug("Skipping cached file {}, cannot restore source identifier from filename: {}, does not match {}", file, fileName, CACHED_FILE_PATTERN);
229 return fileVisitResult;
232 private Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
233 final Matcher matcher = CACHED_FILE_PATTERN.matcher(fileName);
234 if(matcher.matches()) {
235 final String moduleName = matcher.group("moduleName");
236 final String revision = matcher.group("revision");
237 return Optional.of(new SourceIdentifier(moduleName, Optional.fromNullable(revision)));
239 return Optional.absent();
243 public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
244 LOG.warn("Unable to restore cached file {}. Ignoring", file, exc);
245 return FileVisitResult.CONTINUE;
248 public List<SourceIdentifier> getCachedSchemas() {
249 return cachedSchemas;