<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
</dependency>
</dependencies>
package org.opendaylight.yangtools.yang.model.repo.util;
import com.google.common.annotations.Beta;
-import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-
import javax.annotation.concurrent.GuardedBy;
-
import org.opendaylight.yangtools.util.concurrent.ExceptionMapper;
import org.opendaylight.yangtools.util.concurrent.ReflectiveExceptionMapper;
import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
* a specific representation of a source.
*/
@GuardedBy("this")
- private final Map<SourceIdentifier, Multimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>>> sources = new HashMap<>();
+ private final Map<SourceIdentifier, ListMultimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>>> sources = new HashMap<>();
/*
* Schema source listeners.
@SuppressWarnings("unchecked")
final CheckedFuture<? extends T, SchemaSourceException> f = ((SchemaSourceProvider<T>)reg.getProvider()).getSource(id);
+
return Futures.makeChecked(Futures.withFallback(f, new FutureFallback<T>() {
@Override
public ListenableFuture<T> create(final Throwable t) throws SchemaSourceException {
@Override
public <T extends SchemaSourceRepresentation> CheckedFuture<T, SchemaSourceException> getSchemaSource(final SourceIdentifier id, final Class<T> representation) {
- final Multimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>> srcs = sources.get(id);
+ final ListMultimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>> srcs = sources.get(id);
if (srcs == null) {
return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("No providers registered for source" + id));
}
- final Iterator<AbstractSchemaSourceRegistration<?>> regs = srcs.get(representation).iterator();
+ // TODO, remove and make sources keep sorted multimap (e.g. ArrayListMultimap with SortedLists)
+ final ArrayList<AbstractSchemaSourceRegistration<?>> sortedSchemaSourceRegistrations = Lists.newArrayList(srcs.get(representation));
+ Collections.sort(sortedSchemaSourceRegistrations, SchemaProviderCostComparator.INSTANCE);
+
+ final Iterator<AbstractSchemaSourceRegistration<?>> regs = sortedSchemaSourceRegistrations.iterator();
if (!regs.hasNext()) {
return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(
new MissingSchemaSourceException("No providers for source " + id + " representation " + representation + " available"));
}
- return fetchSource(id, regs);
+ CheckedFuture<T, SchemaSourceException> fetchSourceFuture = fetchSource(id, regs);
+ // Add callback to notify cache listeners about encountered schema
+ Futures.addCallback(fetchSourceFuture, new FutureCallback<T>() {
+ @Override
+ public void onSuccess(final T result) {
+ for (final SchemaListenerRegistration listener : listeners) {
+ listener.getInstance().schemaSourceEncountered(result);
+ }
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ LOG.trace("Skipping notification for encountered source {}, fetching source failed", id, t);
+ }
+ });
+
+ return fetchSourceFuture;
}
private synchronized <T extends SchemaSourceRepresentation> void addSource(final PotentialSchemaSource<T> source, final AbstractSchemaSourceRegistration<T> reg) {
- Multimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>> m = sources.get(source.getSourceIdentifier());
+ ListMultimap<Class<? extends SchemaSourceRepresentation>, AbstractSchemaSourceRegistration<?>> m = sources.get(source.getSourceIdentifier());
if (m == null) {
- m = HashMultimap.create();
+ m = ArrayListMultimap.create();
sources.put(source.getSourceIdentifier(), m);
}
}
return ret;
}
+
+ private static class SchemaProviderCostComparator implements Comparator<AbstractSchemaSourceRegistration<?>> {
+ public static final SchemaProviderCostComparator INSTANCE = new SchemaProviderCostComparator();
+
+ @Override
+ public int compare(final AbstractSchemaSourceRegistration<?> o1, final AbstractSchemaSourceRegistration<?> o2) {
+ return o1.getInstance().getCost() - o2.getInstance().getCost();
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.model.repo.util;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+import org.opendaylight.yangtools.yang.model.util.repo.FilesystemSchemaCachingProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cache implementation that stores schemas in form of files under provided folder
+ */
+public final class FilesystemSchemaSourceCache<T extends SchemaSourceRepresentation> extends AbstractSchemaSourceCache<T> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaSourceCache.class);
+
+ // Init storage adapters
+ private static final Map<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> storageAdapters =
+ Collections.<Class<? extends SchemaSourceRepresentation>, StorageAdapter<? extends SchemaSourceRepresentation>> singletonMap(
+ YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());
+
+ private final Class<T> representation;
+ private final File storageDirectory;
+
+ public FilesystemSchemaSourceCache(
+ final SchemaSourceRegistry consumer, final Class<T> representation, final File storageDirectory) {
+ super(consumer, representation, Costs.LOCAL_IO);
+ this.representation = representation;
+ this.storageDirectory = Preconditions.checkNotNull(storageDirectory);
+
+ checkSupportedRepresentation(representation);
+
+ if(storageDirectory.exists() == false) {
+ Preconditions.checkArgument(storageDirectory.mkdirs(), "Unable to create cache directory at %s", storageDirectory);
+ }
+ Preconditions.checkArgument(storageDirectory.exists());
+ Preconditions.checkArgument(storageDirectory.isDirectory());
+ Preconditions.checkArgument(storageDirectory.canWrite());
+ Preconditions.checkArgument(storageDirectory.canRead());
+
+ init();
+ }
+
+ private static void checkSupportedRepresentation(final Class<? extends SchemaSourceRepresentation> representation) {
+ for (final Class<? extends SchemaSourceRepresentation> supportedRepresentation : storageAdapters.keySet()) {
+ if(supportedRepresentation.isAssignableFrom(representation)) {
+ return;
+ }
+ }
+
+ throw new IllegalArgumentException(String.format(
+ "This cache does not support representation: %s, supported representations are: %s", representation, storageAdapters.keySet()));
+ }
+
+ private static final Pattern CACHED_FILE_PATTERN =
+ Pattern.compile(
+ "(?<moduleName>[^@]+)" +
+ "(@(?<revision>" + FilesystemSchemaCachingProvider.REVISION_PATTERN + "))?");
+
+ /**
+ * Restore cache state
+ */
+ private void init() {
+
+ final CachedModulesFileVisitor fileVisitor = new CachedModulesFileVisitor();
+ try {
+ Files.walkFileTree(storageDirectory.toPath(), fileVisitor);
+ } catch (final IOException e) {
+ LOG.warn("Unable to restore cache from {}. Starting with empty cache", storageDirectory);
+ return;
+ }
+
+ for (final SourceIdentifier cachedSchema : fileVisitor.getCachedSchemas()) {
+ register(cachedSchema);
+ }
+ }
+
+ @Override
+ public synchronized CheckedFuture<? extends T, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
+ final File file = FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(sourceIdentifier), storageDirectory);
+ if(file.exists() && file.canRead()) {
+ LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
+ final SchemaSourceRepresentation restored = storageAdapters.get(representation).restore(sourceIdentifier, file);
+ return Futures.immediateCheckedFuture(representation.cast(restored));
+ }
+
+ LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
+ return Futures.<T, SchemaSourceException>immediateFailedCheckedFuture(new MissingSchemaSourceException("Source not found"));
+ }
+
+ @Override
+ protected synchronized void offer(final T source) {
+ LOG.trace("Source {} offered to cache", source.getIdentifier());
+ final File file = sourceIdToFile(source);
+ if(file.exists()) {
+ LOG.debug("Source {} already in cache as {}", source.getIdentifier(), file);
+ return;
+ }
+
+ storeSource(file, source);
+ register(source.getIdentifier());
+ LOG.trace("Source {} stored in cache as {}", source.getIdentifier(), file);
+ }
+
+ private File sourceIdToFile(final T source) {
+ return FilesystemSchemaCachingProvider.sourceIdToFile(toLegacy(source.getIdentifier()), storageDirectory);
+ }
+
+ private void storeSource(final File file, final T schemaRepresentation) {
+ storageAdapters.get(representation).store(file, schemaRepresentation);
+ }
+
+ private static org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier toLegacy(final SourceIdentifier identifier) {
+ return new org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier(identifier.getName(), Optional.fromNullable(identifier.getRevision()));
+ }
+
+ private static abstract class StorageAdapter<T extends SchemaSourceRepresentation> {
+
+ private final Class<T> supportedType;
+
+ protected StorageAdapter(final Class<T> supportedType) {
+ this.supportedType = supportedType;
+ }
+
+ void store(final File file, final SchemaSourceRepresentation schemaSourceRepresentation) {
+ Preconditions.checkArgument(supportedType.isAssignableFrom(schemaSourceRepresentation.getClass()),
+ "Cannot store schema source %s, this adapter only supports %s", schemaSourceRepresentation, supportedType);
+
+ storeAsType(file, supportedType.cast(schemaSourceRepresentation));
+
+ }
+
+ protected abstract void storeAsType(final File file, final T cast);
+
+ public Class<T> getSupportedType() {
+ return supportedType;
+ }
+
+ public T restore(final SourceIdentifier sourceIdentifier, final File cachedSource) {
+ Preconditions.checkArgument(cachedSource.isFile());
+ Preconditions.checkArgument(cachedSource.exists());
+ Preconditions.checkArgument(cachedSource.canRead());
+ return restoreAsType(sourceIdentifier, cachedSource);
+ }
+
+ protected abstract T restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource);
+ }
+
+ private static final class YangTextSchemaStorageAdapter extends StorageAdapter<YangTextSchemaSource> {
+
+ protected YangTextSchemaStorageAdapter() {
+ super(YangTextSchemaSource.class);
+ }
+
+ @Override
+ protected void storeAsType(final File file, final YangTextSchemaSource cast) {
+ try {
+ Files.copy(cast.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (final IOException e) {
+ throw new IllegalStateException("Cannot store schema source " + cast.getIdentifier() + " to " + file, e);
+ }
+ }
+
+ @Override
+ public YangTextSchemaSource restoreAsType(final SourceIdentifier sourceIdentifier, final File cachedSource) {
+ return new YangTextSchemaSource(sourceIdentifier) {
+
+ @Override
+ protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
+ return toStringHelper;
+ }
+
+ @Override
+ public InputStream openStream() throws IOException {
+ return new FileInputStream(cachedSource);
+ }
+ };
+ }
+ }
+
+ private static final class CachedModulesFileVisitor extends SimpleFileVisitor<Path> {
+ private final List<SourceIdentifier> cachedSchemas = Lists.newArrayList();
+
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ final FileVisitResult fileVisitResult = super.visitFile(file, attrs);
+ String fileName = file.toFile().getName();
+ fileName = com.google.common.io.Files.getNameWithoutExtension(fileName);
+
+ final Optional<SourceIdentifier> si = getSourceIdentifier(fileName);
+ if(si.isPresent()) {
+ LOG.trace("Restoring cached file {} as {}", file, si.get());
+ cachedSchemas.add(si.get());
+ } else {
+ LOG.debug("Skipping cached file {}, cannot restore source identifier from filename: {}, does not match {}", file, fileName, CACHED_FILE_PATTERN);
+ }
+ return fileVisitResult;
+ }
+
+ private Optional<SourceIdentifier> getSourceIdentifier(final String fileName) {
+ final Matcher matcher = CACHED_FILE_PATTERN.matcher(fileName);
+ if(matcher.matches()) {
+ final String moduleName = matcher.group("moduleName");
+ final String revision = matcher.group("revision");
+ return Optional.of(new SourceIdentifier(moduleName, Optional.fromNullable(revision)));
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
+ LOG.warn("Unable to restore cached file {}. Ignoring", file, exc);
+ return FileVisitResult.CONTINUE;
+ }
+
+ public List<SourceIdentifier> getCachedSchemas() {
+ return cachedSchemas;
+ }
+ }
+}
*/
public final class FilesystemSchemaCachingProvider<I> extends AbstractCachingSchemaSourceProvider<I, InputStream> {
private static final Logger LOG = LoggerFactory.getLogger(FilesystemSchemaCachingProvider.class);
- private static final Pattern REVISION_PATTERN = Pattern.compile("\\d\\d\\d\\d-\\d\\d-\\d\\d");
+ public static final Pattern REVISION_PATTERN = Pattern.compile("\\d\\d\\d\\d-\\d\\d-\\d\\d");
private final File storageDirectory;
private final SchemaSourceTransformation<I, String> transformationFunction;
}
private File toFile(final SourceIdentifier identifier) {
+ return sourceIdToFile(identifier, storageDirectory);
+ }
+
+ public static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
File file = null;
String rev = identifier.getRevision();
if (rev == null || rev.isEmpty()) {
- file = findFileWithNewestRev(identifier);
+ file = findFileWithNewestRev(identifier, storageDirectory);
} else {
file = new File(storageDirectory, identifier.toYangFilename());
}
return file;
}
- private File findFileWithNewestRev(final SourceIdentifier identifier) {
+ private static File findFileWithNewestRev(final SourceIdentifier identifier, final File storageDirectory) {
File[] files = storageDirectory.listFiles(new FilenameFilter() {
final Pattern p = Pattern.compile(Pattern.quote(identifier.getName()) + "(\\.yang|@\\d\\d\\d\\d-\\d\\d-\\d\\d.yang)");
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yangtools.yang.model.repo.util;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.either;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+import com.google.common.io.Files;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+
+public class FilesystemSchemaSourceCacheTest {
+
+ @Mock
+ private SchemaSourceRegistry registry;
+ @Mock
+ private SchemaSourceRegistration<?> registration;
+ private File storageDir;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ storageDir = Files.createTempDir();
+ doReturn(registration).when(registry).registerSchemaSource(any(SchemaSourceProvider.class), any(PotentialSchemaSource.class));
+ }
+
+ @Test
+ public void testCacheAndRestore() throws Exception {
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache
+ = new FilesystemSchemaSourceCache<>(registry, YangTextSchemaSource.class, storageDir);
+
+ final String content = "content1";
+ final YangTextSchemaSource source = new TestingYangSource("test", "2012-12-12", content);
+ cache.offer(source);
+
+ final String content2 = "content2";
+ final YangTextSchemaSource source2 = new TestingYangSource("test2", null, content);
+ cache.offer(source2);
+
+ final List<File> storedFiles = getFilesFromCache();
+ assertEquals(2, storedFiles.size());
+ final Collection<String> fileNames = filesToFilenamesWithoutRevision(storedFiles);
+
+ assertThat(fileNames, both(hasItem("test2@0000-00-00")).and(hasItem("test@2012-12-12")));
+
+ assertThat(Files.toString(storedFiles.get(0), Charsets.UTF_8), either(containsString(content)).or(containsString(content2)));
+ assertThat(Files.toString(storedFiles.get(1), Charsets.UTF_8), either(containsString(content)).or(containsString(content2)));
+
+ verify(registry, times(2)).registerSchemaSource(any(SchemaSourceProvider.class), any(PotentialSchemaSource.class));
+
+ // Create new cache from stored sources
+ new FilesystemSchemaSourceCache<>(registry, YangTextSchemaSource.class, storageDir);
+
+ verify(registry, times(4)).registerSchemaSource(any(SchemaSourceProvider.class), any(PotentialSchemaSource.class));
+
+ final List<File> storedFilesAfterNewCache = getFilesFromCache();
+ assertEquals(2, storedFilesAfterNewCache.size());
+ }
+
+ private Collection<String> filesToFilenamesWithoutRevision(final List<File> storedFiles) {
+ return Collections2.transform(storedFiles, new Function<File, String>() {
+ @Override
+ public String apply(final File input) {
+ return Files.getNameWithoutExtension(input.getName());
+ }
+ });
+ }
+
+ @Test
+ public void testCacheDuplicate() throws Exception {
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache
+ = new FilesystemSchemaSourceCache<>(registry, YangTextSchemaSource.class, storageDir);
+
+ final String content = "content1";
+ final YangTextSchemaSource source = new TestingYangSource("test", null, content);
+ // Double offer
+ cache.offer(source);
+ cache.offer(source);
+
+ final List<File> storedFiles = getFilesFromCache();
+ assertEquals(1, storedFiles.size());
+ verify(registry).registerSchemaSource(any(SchemaSourceProvider.class), any(PotentialSchemaSource.class));
+ }
+
+ @Test
+ public void testCacheMultipleRevisions() throws Exception {
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache
+ = new FilesystemSchemaSourceCache<>(registry, YangTextSchemaSource.class, storageDir);
+
+ final String content = "content1";
+ final YangTextSchemaSource source = new TestingYangSource("test", null, content);
+ final YangTextSchemaSource source2 = new TestingYangSource("test", "2012-12-12", content);
+ final YangTextSchemaSource source3 = new TestingYangSource("test", "2013-12-12", content);
+ // Double offer
+ cache.offer(source);
+ cache.offer(source2);
+ cache.offer(source3);
+
+ final List<File> storedFiles = getFilesFromCache();
+ assertEquals(3, storedFiles.size());
+
+ assertThat(filesToFilenamesWithoutRevision(storedFiles), both(hasItem("test@0000-00-00")).and(hasItem("test@2012-12-12")).and(hasItem("test@2013-12-12")));
+
+ verify(registry, times(3)).registerSchemaSource(any(SchemaSourceProvider.class), any(PotentialSchemaSource.class));
+ }
+
+ private List<File> getFilesFromCache() {
+ return Arrays.asList(storageDir.listFiles());
+ }
+
+ private class TestingYangSource extends YangTextSchemaSource {
+
+ private final String content;
+
+ protected TestingYangSource(final String name, final String revision, final String content) {
+ super(new SourceIdentifier(name, Optional.fromNullable(revision)));
+ this.content = content;
+ }
+
+ @Override
+ protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
+ return toStringHelper;
+ }
+
+ @Override
+ public InputStream openStream() throws IOException {
+ return new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
+ }
+ }
+}
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.matchers.JUnitMatchers.both;
+import static org.junit.matchers.JUnitMatchers.hasItem;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import org.junit.Ignore;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.google.common.io.InputSupplier;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceListener;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
+import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource;
import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.CheckedFuture;
-
public class SharedSchemaRepositoryTest {
@Test
fail("Schema context creation should have failed");
}
- // TODO
- @Ignore("Costs are not considered when getting sources")
@Test
public void testDifferentCosts() throws Exception {
final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
verify(immediateInetTypesYang).getSource(id);
}
+ @Test
+ public void testWithCacheStartup() throws Exception {
+ final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
+
+ class CountingSchemaListener implements SchemaSourceListener {
+ List<PotentialSchemaSource<?>> registeredSources = Lists.newArrayList();
+
+ @Override
+ public void schemaSourceEncountered(final SchemaSourceRepresentation source) {
+ }
+
+ @Override
+ public void schemaSourceRegistered(final Iterable<PotentialSchemaSource<?>> sources) {
+ for (final PotentialSchemaSource<?> source : sources) {
+ registeredSources.add(source);
+ }
+ }
+
+ @Override
+ public void schemaSourceUnregistered(final PotentialSchemaSource<?> source) {
+ }
+ }
+
+ final File storageDir = Files.createTempDir();
+
+ final CountingSchemaListener listener = new CountingSchemaListener();
+ sharedSchemaRepository.registerSchemaSourceListener(listener);
+
+ final File test = new File(storageDir, "test.yang");
+ Files.copy(new StringSupplier("content-test"), test);
+
+ final File test2 = new File(storageDir, "test@2012-12-12.yang");
+ Files.copy(new StringSupplier("content-test-2012"), test2);
+
+ final File test3 = new File(storageDir, "test@2013-12-12.yang");
+ Files.copy(new StringSupplier("content-test-2013"), test3);
+
+ final File test4 = new File(storageDir, "module@2010-12-12.yang");
+ Files.copy(new StringSupplier("content-module-2010"), test4);
+
+
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache = new FilesystemSchemaSourceCache<>(sharedSchemaRepository, YangTextSchemaSource.class, storageDir);
+ sharedSchemaRepository.registerSchemaSourceListener(cache);
+
+ assertEquals(4, listener.registeredSources.size());
+
+ final Function<PotentialSchemaSource<?>, SourceIdentifier> potSourceToSID = new Function<PotentialSchemaSource<?>, SourceIdentifier>() {
+ @Override
+ public SourceIdentifier apply(final PotentialSchemaSource<?> input) {
+ return input.getSourceIdentifier();
+ }
+ };
+ assertThat(Collections2.transform(listener.registeredSources, potSourceToSID),
+ both(hasItem(new SourceIdentifier("test", Optional.<String>absent())))
+ .and(hasItem(new SourceIdentifier("test", Optional.of("2012-12-12"))))
+ .and(hasItem(new SourceIdentifier("test", Optional.of("2013-12-12"))))
+ .and(hasItem(new SourceIdentifier("module", Optional.of("2010-12-12"))))
+ );
+ }
+
+ @Test
+ public void testWithCacheRunning() throws Exception {
+ final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
+
+ final File storageDir = Files.createTempDir();
+
+ final FilesystemSchemaSourceCache<YangTextSchemaSource> cache = new FilesystemSchemaSourceCache<>(sharedSchemaRepository, YangTextSchemaSource.class, storageDir);
+ sharedSchemaRepository.registerSchemaSourceListener(cache);
+
+ final SourceIdentifier runningId = new SourceIdentifier("running", Optional.of("2012-12-12"));
+
+ sharedSchemaRepository.registerSchemaSource(new SchemaSourceProvider<YangTextSchemaSource>() {
+ @Override
+ public CheckedFuture<YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
+ return Futures.<YangTextSchemaSource, SchemaSourceException>immediateCheckedFuture(new YangTextSchemaSource(runningId) {
+ @Override
+ protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) {
+ return toStringHelper;
+ }
+
+ @Override
+ public InputStream openStream() throws IOException {
+ return IOUtils.toInputStream("running");
+ }
+ });
+ }
+ }, PotentialSchemaSource.create(runningId, YangTextSchemaSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue()));
+
+ final TextToASTTransformer transformer = TextToASTTransformer.create(sharedSchemaRepository, sharedSchemaRepository);
+ sharedSchemaRepository.registerSchemaSourceListener(transformer);
+
+ // Request schema to make repository notify the cache
+ final CheckedFuture<SchemaContext, SchemaResolutionException> schemaFuture = sharedSchemaRepository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT).createSchemaContext(Lists.newArrayList(runningId));
+ Futures.addCallback(schemaFuture, new FutureCallback<SchemaContext>() {
+ @Override
+ public void onSuccess(final SchemaContext result) {
+ fail("Creation of schema context should fail from non-regular sources");
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ // Creation of schema context fails, since we do not provide regular sources, but we just want to check cache
+ final List<File> cachedSchemas = Arrays.asList(storageDir.listFiles());
+ assertEquals(1, cachedSchemas.size());
+ assertEquals(Files.getNameWithoutExtension(cachedSchemas.get(0).getName()), "running@2012-12-12");
+ }
+ });
+
+ try {
+ schemaFuture.get();
+ } catch (final ExecutionException e) {
+ assertNotNull(e.getCause());
+ assertEquals(MissingSchemaSourceException.class, e.getCause().getClass());
+ return;
+ }
+
+ fail("Creation of schema context should fail from non-regular sources");
+ }
+
private void assertSchemaContext(final SchemaContext schemaContext, final int moduleSize) {
assertNotNull(schemaContext);
assertEquals(moduleSize, schemaContext.getModules().size());
return SettableSchemaProvider.createImmediate(aSTSchemaSource.get(), ASTSchemaSource.class);
}
+ private class StringSupplier implements InputSupplier<InputStream> {
+ private final String s;
+
+ public StringSupplier(final String s) {
+ this.s = s;
+ }
+
+ @Override
+ public InputStream getInput() throws IOException {
+ return IOUtils.toInputStream(s);
+ }
+ }
}