org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache.java

Source

/*
 * 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.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
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.FilenameFilter;
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.text.DateFormat;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
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.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>> STORAGE_ADAPTERS = Collections
            .singletonMap(YangTextSchemaSource.class, new YangTextSchemaStorageAdapter());

    private static final Pattern CACHED_FILE_PATTERN = Pattern
            .compile("(?<moduleName>[^@]+)" + "(@(?<revision>" + SourceIdentifier.REVISION_PATTERN + "))?");

    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()) {
            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 : STORAGE_ADAPTERS
                .keySet()) {
            if (supportedRepresentation.isAssignableFrom(representation)) {
                return;
            }
        }

        throw new IllegalArgumentException(
                String.format("This cache does not support representation: %s, supported representations are: %s",
                        representation, STORAGE_ADAPTERS.keySet()));
    }

    /**
     * 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 = sourceIdToFile(sourceIdentifier, storageDirectory);
        if (file.exists() && file.canRead()) {
            LOG.trace("Source {} found in cache as {}", sourceIdentifier, file);
            final SchemaSourceRepresentation restored = STORAGE_ADAPTERS.get(representation)
                    .restore(sourceIdentifier, file);
            return Futures.immediateCheckedFuture(representation.cast(restored));
        }

        LOG.debug("Source {} not found in cache as {}", sourceIdentifier, file);
        return Futures.immediateFailedCheckedFuture(
                new MissingSchemaSourceException("Source not found", sourceIdentifier));
    }

    @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 sourceIdToFile(source.getIdentifier(), storageDirectory);
    }

    static File sourceIdToFile(final SourceIdentifier identifier, final File storageDirectory) {
        final String rev = identifier.getRevision();
        final File file;
        if (Strings.isNullOrEmpty(rev)) {
            file = findFileWithNewestRev(identifier, storageDirectory);
        } else {
            file = new File(storageDirectory, identifier.toYangFilename());
        }
        return file;
    }

    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)");

            @Override
            public boolean accept(final File dir, final String name) {
                return p.matcher(name).matches();
            }
        });

        if (files.length == 0) {
            return new File(storageDirectory, identifier.toYangFilename());
        }
        if (files.length == 1) {
            return files[0];
        }

        File file = null;
        TreeMap<Date, File> map = new TreeMap<>();
        for (File sorted : files) {
            String fileName = sorted.getName();
            Matcher m = SourceIdentifier.REVISION_PATTERN.matcher(fileName);
            if (m.find()) {
                String revStr = m.group();
                /*
                 * FIXME: Consider using string for comparison.
                 * String is comparable, pattern check tested format
                 * so comparing as ASCII string should be sufficient
                 */
                DateFormat df = SimpleDateFormatUtil.getRevisionFormat();
                try {
                    Date d = df.parse(revStr);
                    map.put(d, sorted);
                } catch (final ParseException e) {
                    LOG.info("Unable to parse date from yang file name {}", fileName);
                    map.put(new Date(0L), sorted);
                }

            } else {
                map.put(new Date(0L), sorted);
            }
        }
        file = map.lastEntry().getValue();

        return file;
    }

    private void storeSource(final File file, final T schemaRepresentation) {
        STORAGE_ADAPTERS.get(representation).store(file, schemaRepresentation);
    }

    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 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 (final InputStream castStream = cast.openStream()) {
                Files.copy(castStream, 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 MoreObjects.ToStringHelper addToStringAttributes(
                        final MoreObjects.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 static 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(RevisionSourceIdentifier.create(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;
        }
    }
}