BUG-997 Implement Filesystem source cache for schema repository
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / repo / FilesystemSchemaCachingProvider.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/eplv10.html
7  */
8 package org.opendaylight.yangtools.yang.model.util.repo;
9
10 import java.io.ByteArrayInputStream;
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.FileOutputStream;
15 import java.io.FilenameFilter;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.OutputStreamWriter;
19 import java.text.DateFormat;
20 import java.text.ParseException;
21 import java.text.SimpleDateFormat;
22 import java.util.Date;
23 import java.util.TreeMap;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import com.google.common.base.Charsets;
31 import com.google.common.base.Function;
32 import com.google.common.base.Optional;
33 import com.google.common.base.Preconditions;
34
35 /**
36  * Filesystem-based schema caching source provider
37  *
38  * This schema source provider caches all YANG modules loaded from backing
39  * schema source providers (registered via
40  * {@link #createInstanceFor(SchemaSourceProvider)} to supplied folder.
41  *
42  * @param <I>
43  *            Input format in which schema source is represented.
44  *
45  */
46 public final class FilesystemSchemaCachingProvider<I> extends AbstractCachingSchemaSourceProvider<I, InputStream> {
47     private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaCachingProvider.class);
48     public static final Pattern REVISION_PATTERN = Pattern.compile("\\d\\d\\d\\d-\\d\\d-\\d\\d");
49
50     private final File storageDirectory;
51     private final SchemaSourceTransformation<I, String> transformationFunction;
52
53     /**
54      *
55      * Construct filesystem caching schema source provider.
56      *
57      *
58      * @param delegate
59      *            Default delegate to lookup for missed entries in cache.
60      * @param directory
61      *            Directory where YANG files should be cached.
62      * @param transformationFunction
63      *            Transformation function which translates from input in format
64      *            <code>I</code> to InputStream.
65      * @throws IllegalArgumentException
66      *             If supplied directory does not exists or is not directory.
67      */
68     public FilesystemSchemaCachingProvider(final AdvancedSchemaSourceProvider<I> delegate, final File directory,
69             final SchemaSourceTransformation<I, String> transformationFunction) {
70         super(delegate);
71         Preconditions.checkNotNull(directory, "directory must not be null.");
72         Preconditions.checkArgument(directory.exists(), "directory must be directory.");
73         Preconditions.checkArgument(directory.isDirectory(), "directory must be directory.");
74         this.storageDirectory = directory;
75         this.transformationFunction = Preconditions.checkNotNull(transformationFunction,
76                 "transformationFunction must not be null.");
77     }
78
79     /**
80      *
81      * Construct filesystem caching schema source provider.
82      *
83      *
84      * @param delegate
85      *            Default delegate to lookup for missed entries in cache.
86      * @param directory
87      *            Directory where YANG files should be cached.
88      * @param transformationFunction
89      *            Transformation function which translates from input in format
90      *            <code>I</code> to InputStream.
91      * @throws IllegalArgumentException
92      *             If supplied directory does not exists or is not directory.
93      * @deprecated Use
94      *             {@link #FilesystemSchemaCachingProvider(AdvancedSchemaSourceProvider, File, SchemaSourceTransformation)}
95      *             with
96      *             {@link SchemaSourceProviders#schemaSourceTransformationFrom(Function)}
97      *             instead.
98      */
99     @Deprecated
100     public FilesystemSchemaCachingProvider(final AdvancedSchemaSourceProvider<I> delegate, final File directory,
101             final Function<I, String> transformationFunction) {
102         super(delegate);
103         Preconditions.checkNotNull(directory, "directory must not be null.");
104         Preconditions.checkArgument(directory.exists(), "directory must be directory.");
105         Preconditions.checkArgument(directory.isDirectory(), "directory must be directory.");
106         this.storageDirectory = directory;
107         this.transformationFunction = SchemaSourceProviders.schemaSourceTransformationFrom(transformationFunction);
108     }
109
110     @Override
111     protected synchronized Optional<InputStream> cacheSchemaSource(final SourceIdentifier identifier,
112             final Optional<I> source) {
113         File schemaFile = toFile(identifier);
114         try {
115             if (source.isPresent() && schemaFile.createNewFile()) {
116                 try (FileOutputStream outStream = new FileOutputStream(schemaFile);
117                         OutputStreamWriter writer = new OutputStreamWriter(outStream);) {
118                     writer.write(transformToString(source.get()));
119                     writer.flush();
120                 } catch (IOException e) {
121                     LOG.warn("Could not chache source for {}. Source: ",identifier,source.get(),e);
122                 }
123             }
124         } catch (IOException e) {
125             LOG.warn("Could not create cache file for {}. File: ",identifier,schemaFile,e);
126         }
127         return transformToStream(source);
128     }
129
130     private Optional<InputStream> transformToStream(final Optional<I> source) {
131         if (source.isPresent()) {
132             return Optional.<InputStream> of(new ByteArrayInputStream(transformToString(source.get()).getBytes(
133                     Charsets.UTF_8)));
134         }
135         return Optional.absent();
136     }
137
138     private String transformToString(final I input) {
139         return transformationFunction.transform(input);
140     }
141
142     @Override
143     protected Optional<InputStream> getCachedSchemaSource(final SourceIdentifier identifier) {
144         File inputFile = toFile(identifier);
145         try {
146             if (inputFile.exists() && inputFile.canRead()) {
147                 InputStream stream = new FileInputStream(inputFile);
148                 return Optional.of(stream);
149             }
150         } catch (FileNotFoundException e) {
151             return Optional.absent();
152         }
153         return Optional.absent();
154     }
155
156     private File toFile(final SourceIdentifier identifier) {
157         return sourceIdToFile(identifier, storageDirectory);
158     }
159
160     public static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
161         File file = null;
162         String rev = identifier.getRevision();
163         if (rev == null || rev.isEmpty()) {
164             file = findFileWithNewestRev(identifier, storageDirectory);
165         } else {
166             file = new File(storageDirectory, identifier.toYangFilename());
167         }
168         return file;
169     }
170
171     private static File findFileWithNewestRev(final SourceIdentifier identifier, final File storageDirectory) {
172         File[] files = storageDirectory.listFiles(new FilenameFilter() {
173             final Pattern p = Pattern.compile(Pattern.quote(identifier.getName()) + "(\\.yang|@\\d\\d\\d\\d-\\d\\d-\\d\\d.yang)");
174
175             @Override
176             public boolean accept(final File dir, final String name) {
177                 return p.matcher(name).matches();
178             }
179         });
180
181         if (files.length == 0) {
182             return new File(storageDirectory, identifier.toYangFilename());
183         }
184         if (files.length == 1) {
185             return files[0];
186         }
187
188         File file = null;
189         TreeMap<Date, File> map = new TreeMap<>();
190         for (File sorted : files) {
191             String fileName = sorted.getName();
192             Matcher m = REVISION_PATTERN.matcher(fileName);
193             if (m.find()) {
194                 String revStr = m.group();
195                 DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
196                 try {
197                     Date d = df.parse(revStr);
198                     map.put(d, sorted);
199                 } catch (ParseException e) {
200                     LOG.info("Unable to parse date from yang file name");
201                     map.put(new Date(0L), sorted);
202                 }
203
204             } else {
205                 map.put(new Date(0L), sorted);
206             }
207         }
208         file = map.lastEntry().getValue();
209
210         return file;
211     }
212
213     public static FilesystemSchemaCachingProvider<String> createFromStringSourceProvider(
214             final SchemaSourceProvider<String> liveProvider, final File directory) {
215         Preconditions.checkNotNull(liveProvider);
216         Preconditions.checkNotNull(directory);
217         directory.mkdirs();
218         return new FilesystemSchemaCachingProvider<String>(
219                 SchemaSourceProviders.toAdvancedSchemaSourceProvider(liveProvider),//
220                 directory, //
221                 SchemaSourceProviders.<String>identityTransformation());
222     }
223 }