3478953a0607b7e02e2eb6b4646fbda0557ee1e9
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / repo / util / FilesystemSchemaSourceCache.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.model.repo.util;
9
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;
16 import java.io.File;
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;
28 import java.util.Map;
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;
41
42 /**
43  * Cache implementation that stores schemas in form of files under provided folder
44  */
45 public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentation> extends AbstractSchemaSourceCache<T> {
46
47     private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
48
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());
53
54     private final Class<T> representation;
55     private final File storageDirectory;
56
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);
62
63         checkSupportedRepresentation(representation);
64
65         if(!storageDirectory.exists()) {
66             Preconditions.checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
67         }
68         Preconditions.checkArgument(storageDirectory.exists());
69         Preconditions.checkArgument(storageDirectory.isDirectory());
70         Preconditions.checkArgument(storageDirectory.canWrite());
71         Preconditions.checkArgument(storageDirectory.canRead());
72
73         init();
74     }
75
76     private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
77         for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : storageAdapters.keySet()) {
78             if(supportedRepresentation.isAssignableFrom(representation)) {
79                 return;
80             }
81         }
82
83        throw new IllegalArgumentException(String.format(
84                 "This cache does not support representation: %s, supported representations are: %s", representation, storageAdapters.keySet()));
85     }
86
87     private static final Pattern CACHED_FILE_PATTERN =
88             Pattern.compile(
89                     "(?<moduleName>[^@]+)" +
90                     "(@(?<revision>" + FilesystemSchemaCachingProvider.REVISION_PATTERN + "))?");
91
92     /**
93      * Restore cache state
94      */
95     private void init() {
96
97         final CachedModulesFileVisitor fileVisitor = new CachedModulesFileVisitor();
98         try {
99             Files.walkFileTree(storageDirectory.toPath(), fileVisitor);
100         } catch (final IOException e) {
101             LOG.warn("Unable to restore cache from {}. Starting with empty cache", storageDirectory);
102             return;
103         }
104
105         for (final SourceIdentifier cachedSchema : fileVisitor.getCachedSchemas()) {
106             register(cachedSchema);
107         }
108     }
109
110     @Override
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));
117         }
118
119         LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
120         return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
121     }
122
123     @Override
124     protected synchronized void offer(final T source) {
125         LOG.trace("Source {} offered to cache", source.getIdentifier());
126         final File file = sourceIdToFile(source);
127         if(file.exists()) {
128             LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
129             return;
130         }
131
132         storeSource(file, source);
133         register(source.getIdentifier());
134         LOG.trace("Source {} stored in cache as {}", source.getIdentifier(), file);
135     }
136
137     private File sourceIdToFile(final T source) {
138         return FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(source.getIdentifier()), storageDirectory);
139     }
140
141     private void storeSource(final File file, final T schemaRepresentation) {
142         storageAdapters.get(representation).store(file, schemaRepresentation);
143     }
144
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()));
147     }
148
149     private static abstract class StorageAdapter<T extends SchemaSourceRepresentation> {
150
151         private final Class<T> supportedType;
152
153         protected StorageAdapter(final Class<T> supportedType) {
154             this.supportedType = supportedType;
155         }
156
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);
160
161             storeAsType(file, supportedType.cast(schemaSourceRepresentation));
162
163         }
164
165         protected abstract void storeAsType(final File file, final T cast);
166
167         public Class<T> getSupportedType() {
168             return supportedType;
169         }
170
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);
176         }
177
178         protected abstract T restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource);
179     }
180
181     private static final class YangTextSchemaStorageAdapter extends StorageAdapter<YangTextSchemaSource> {
182
183         protected YangTextSchemaStorageAdapter() {
184             super(YangTextSchemaSource.class);
185         }
186
187         @Override
188         protected void storeAsType(final File file, final YangTextSchemaSource cast) {
189             try {
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);
193             }
194         }
195
196         @Override
197         public YangTextSchemaSource restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource) {
198             return new YangTextSchemaSource(sourceIdentifier) {
199
200                 @Override
201                 protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
202                     return toStringHelper;
203                 }
204
205                 @Override
206                 public InputStream openStream() throws IOException {
207                     return new FileInputStream(cachedSource);
208                 }
209             };
210         }
211     }
212
213     private static final class CachedModulesFileVisitor extends SimpleFileVisitor<Path> {
214         private final List<SourceIdentifier> cachedSchemas = Lists.newArrayList();
215
216         @Override
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);
221
222             final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
223             if(si.isPresent()) {
224                 LOG.trace("Restoring cached file {} as {}", file, si.get());
225                 cachedSchemas.add(si.get());
226             } else {
227                 LOG.debug("Skipping cached file {}, cannot restore source identifier from filename: {}, does not match {}", file, fileName, CACHED_FILE_PATTERN);
228             }
229             return fileVisitResult;
230         }
231
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)));
238             }
239             return Optional.absent();
240         }
241
242         @Override
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;
246         }
247
248         public List<SourceIdentifier> getCachedSchemas() {
249             return cachedSchemas;
250         }
251     }
252 }