de.micromata.genome.gdbfs.StdFileSystem.java Source code

Java tutorial

Introduction

Here is the source code for de.micromata.genome.gdbfs.StdFileSystem.java

Source

//
// Copyright (C) 2010-2016 Roger Rene Kommer & Micromata GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package de.micromata.genome.gdbfs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import de.micromata.genome.util.matcher.Matcher;
import de.micromata.genome.util.runtime.CallableX;
import de.micromata.genome.util.runtime.RuntimeIOException;
import de.micromata.genome.util.types.TimeInMillis;

/**
 * FileSystem implementation using the standard operation system file system.
 * 
 * @author Roger Rene Kommer (r.kommer@micromata.de)
 * 
 */
public class StdFileSystem extends AbstractFileSystem {

    /**
     * The Constant STD_LOCKFILENAME.
     */
    public static final String STD_LOCKFILENAME = ".fslock";

    /**
     * always true, only for unittest may will be false.
     */
    private boolean localSync = true;

    /**
     * The root.
     */
    private String root;

    /**
     * The root file.
     */
    private File rootFile;

    /**
     * The canon root.
     */
    private String canonRoot;

    /**
     * The global lock file name.
     */
    private String globalLockFileName = STD_LOCKFILENAME;

    /**
     * The global lock.
     */
    private StdFileSystemLock globalLock;

    /**
     * The local modification count.
     */
    private int localModificationCount;

    /**
     * The locked locks.
     */
    private ThreadLocal<Map<String, StdFileSystemLock>> lockedLocks = new ThreadLocal<Map<String, StdFileSystemLock>>() {

        @Override
        protected Map<String, StdFileSystemLock> initialValue() {
            return new HashMap<String, StdFileSystemLock>();
        }
    };

    /**
     * Instantiates a new std file system.
     */
    public StdFileSystem() {

    }

    /**
     * Instantiates a new std file system.
     *
     * @param root the root
     */
    public StdFileSystem(String root) {
        setRoot(root);
    }

    @Override
    public String toString() {
        return canonRoot;
    }

    /**
     * Inc local modification count.
     *
     * @return the int
     */
    public int incLocalModificationCount() {
        return ++localModificationCount;
    }

    /**
     * Erase rec.
     *
     * @param name the name
     */
    public void eraseRec(String name) {
        List<FsObject> dirs = listFiles(name, null, 'D', false);
        for (FsObject dir : dirs) {
            eraseRec(dir.getName());
        }
        List<FsObject> files = listFiles(name, null, 'F', false);
        for (FsObject file : files) {
            delete(file.getName());
        }
        delete(name);
    }

    @Override
    public void erase() {
        eraseRec("");
        setRoot(canonRoot);
    }

    @Override
    public boolean exists(String name) {
        File f = new File(rootFile, name);
        try {
            ensureFileInFs(f);
            return f.exists();
        } catch (AccessControlException ex) {
            // on some fs access is not allowed.
            return false;
        }
    }

    @Override
    public FsObject getFileObject(String name) {
        File f;
        if (name.length() == 0 || name.equals("/") == true) {
            f = rootFile;
        } else {
            f = new File(rootFile, name);
        }
        ensureFileInFs(f);
        if (f.exists() == false) {
            return null;
        }
        return fileToFsObject(f);
    }

    @Override
    public String getFileSystemName() {
        return root;
    }

    @Override
    public boolean mkdir(String name) {
        checkReadOnly();
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        boolean ret = f.mkdir();
        if (ret == true) {
            incLocalModificationCount();
            addEvent(FileSystemEventType.Created, name, System.currentTimeMillis());
        }
        return ret;
    }

    /**
     * Ensure file in fs.
     *
     * @param f the f
     */
    protected void ensureFileInFs(File f) {
        String cn;
        try {
            // TODO note: this is on windows a very expensive method:
            // long ms = System.currentTimeMillis();
            cn = f.getCanonicalPath();
            // long end = System.currentTimeMillis();
            // long dif = end - ms;
        } catch (IOException ex) {
            throw new FsInvalidNameException("Cannot resolve filename: " + f.getName() + "; " + ex.getMessage(),
                    ex);
        }
        ensureCanonFileInFs(cn);
    }

    /**
     * Ensure canon file in fs.
     *
     * @param canonPath the canon path
     */
    protected void ensureCanonFileInFs(String canonPath) {
        if (canonPath.startsWith(canonRoot) == false) {
            throw new FsInvalidNameException("File is not content of file system: " + canonPath);
        }
    }

    @Override
    public boolean mkdirs(String name) {
        checkReadOnly();
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        boolean ret = f.mkdirs();
        if (ret == true) {
            incLocalModificationCount();
            addEvent(FileSystemEventType.Created, name, System.currentTimeMillis());
        }
        return ret;
    }

    @Override
    public boolean rename(String oldName, String newName) {
        checkReadOnly();
        File f = new File(rootFile, oldName);
        ensureFileInFs(f);
        File toFile = new File(rootFile, newName);
        ensureFileInFs(toFile);
        boolean ret = f.renameTo(toFile);
        if (ret == true) {
            incLocalModificationCount();
            addEvent(FileSystemEventType.Renamed, newName, System.currentTimeMillis(), oldName);

        }
        return ret;
    }

    @Override
    public String readTextFile(String name) {

        byte[] data = readBinaryFile(name);
        try {
            return new String(data, STANDARD_STRING_ENCODING);
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void readBinaryFile(String name, OutputStream os) {
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        try {
            FileInputStream fin = new FileInputStream(f);
            IOUtils.copy(fin, os);
            IOUtils.closeQuietly(fin);
        } catch (IOException ex) {
            throw new RuntimeIOException("Failed to read file: " + f.getName() + "; " + ex.getMessage(), ex);
        }
    }

    /**
     * Check unexistant file.
     *
     * @param file the file
     */
    private void checkUnexistantFile(String file) {
        File f = new File(rootFile, file);
        if (f.exists() == true) {
            throw new FsFileExistsException("File exists: " + file);
        }
    }

    /**
     * Creates the parent directories.
     *
     * @param f the f
     */
    protected void createParentDirectories(File f) {
        if (isAutoCreateDirectories() == false) {
            return;
        }
        File pdir = f.getParentFile();
        if (pdir.exists() == true) {
            return;
        }
        pdir.mkdirs();
    }

    @Override
    public void writeBinaryFile(String name, InputStream is, boolean overWrite) {
        checkReadOnly();
        FileSystemEventType eventType = FileSystemEventType.Modified;

        File f = new File(rootFile, name);
        createParentDirectories(f);
        ensureFileInFs(f);
        if (overWrite == false) {
            checkUnexistantFile(name);
            eventType = FileSystemEventType.Created;
        } else {
            if (f.exists() == false) {
                eventType = FileSystemEventType.Created;
            }
        }

        try {
            FileOutputStream fout = new FileOutputStream(f);
            IOUtils.copy(is, fout);
            IOUtils.closeQuietly(fout);
            incLocalModificationCount();
            addEvent(eventType, name, System.currentTimeMillis());
        } catch (FileNotFoundException ex) {
            throw new FsFileExistsException("Parent file not found: " + name, ex);
        } catch (IOException ex) {
            throw new RuntimeIOException(ex);
        }
    }

    @Override
    public void writeTextFile(String name, String content, boolean overWrite) {
        checkReadOnly();
        if (overWrite == false) {
            checkUnexistantFile(name);
        }
        byte[] data = null;
        if (content != null) {
            try {
                data = content.getBytes(STANDARD_STRING_ENCODING);
            } catch (UnsupportedEncodingException ex) {
                throw new RuntimeException(ex);
            }

        }
        writeBinaryFile(name, data, overWrite);
    }

    /**
     * Canon path.
     *
     * @param f the f
     * @return the string
     */
    private String canonPath(File f) {
        try {
            return f.getCanonicalPath();
        } catch (IOException ex) {
            throw new RuntimeIOException(ex);
        }
    }

    /**
     * Gets the rel name.
     *
     * @param f the f
     * @return the rel name
     */
    protected String getRelName(File f) {
        String cn = canonPath(f);
        String r = cn.substring(canonRoot.length());
        return r.replace('\\', '/');
    }

    /**
     * File to fs object.
     *
     * @param f the f
     * @return the fs object
     */
    protected FsObject fileToFsObject(File f) {
        ensureFileInFs(f);
        FsObject ret;
        String rn = getRelName(f);
        if (rn.startsWith("/") == true) {
            rn = rn.substring(1);
        }
        if (f.isDirectory() == true) {
            ret = new FsDirectoryObject(this, rn, f.lastModified());
        } else if (f.isFile() == true) {
            ret = new FsFileObject(this, rn, "", f.lastModified());
            ret.setLength((int) f.length());
        } else {
            return null;
        }
        ret.setModifiedAt(new Date(f.lastModified()));
        ret.setCreatedAt(ret.getModifiedAt());
        return ret;
    }

    @Override
    public List<FsObject> listFiles(final String name, final Matcher<String> matcher, final Character searchType,
            boolean recursive) {
        final List<FsObject> ret = new ArrayList<FsObject>();
        File f = new File(rootFile, name);
        try {
            if (f.exists() == false || f.isDirectory() == false) {
                return ret;
            }
        } catch (AccessControlException ex) {
            return ret;
        }
        listFiles(canonPath(f), f, matcher, searchType, ret, recursive);
        return ret;
    }

    /**
     * Checks if is system file.
     *
     * @param f the f
     * @return true, if is system file
     */
    public boolean isSystemFile(File f) {
        String name = f.getName();
        return globalLockFileName.equals(name);
    }

    /**
     * List files.
     *
     * @param absRootName the abs root name
     * @param f the f
     * @param matcher the matcher
     * @param searchType the search type
     * @param ret the ret
     * @param recursive the recursive
     */
    protected void listFiles(String absRootName, File f, Matcher<String> matcher, final Character searchType,
            List<FsObject> ret, boolean recursive) {

        File[] files = f.listFiles();
        if (files == null) {
            return;
        }
        for (File el : files) {
            if (isSystemFile(el) == true) {
                continue;
            }
            boolean matches = true;
            if (matcher != null) {
                String tb = canonPath(el);
                String relp = tb.substring(absRootName.length());
                relp = StringUtils.replace(relp, "\\", "/");
                if (relp.startsWith("/") == true) {
                    relp = relp.substring(1);
                }
                if (matcher.match(relp) == false) {
                    matches = false;
                }
            }
            if (el.isDirectory() == true) {
                if (matches == true && (searchType == null || searchType == 'D')) {
                    ret.add(fileToFsObject(el));
                }
                if (recursive == true) {
                    listFiles(absRootName, el, matcher, searchType, ret, recursive);
                }
            } else if (el.isFile() == true) {
                if (matches == true && (searchType == null || searchType == 'F')) {
                    ret.add(fileToFsObject(el));
                }
            }
        }
    }

    @Override
    public boolean delete(String name) {
        checkReadOnly();
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        boolean ret = f.delete();
        if (ret == true) {
            incLocalModificationCount();
            addEvent(FileSystemEventType.Deleted, name, System.currentTimeMillis());
        }
        return ret;
    }

    @Override
    public long getLastModified(final String name) {
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        if (f.exists() == false) {
            return 0;
        }
        return f.lastModified();
    }

    @Override
    protected void finalize() throws Throwable {
        if (globalLock == null) {
            return;
        }
        globalLock.closeLock();
        globalLock = null;
    }

    public StdFileSystemLock getGlobalLock() {
        if (globalLock != null) {
            return globalLock;
        }
        File f = new File(rootFile, ".fslock");
        globalLock = new StdFileSystemLock(f);
        return globalLock;
    }

    /**
     * Release lock.
     *
     * @param lock the lock
     * @param noFsMod the no fs mod
     */
    public void releaseLock(StdFileSystemLock lock, boolean noFsMod) {
        String canonf = canonPath(lock.getFile());
        StdFileSystemLock cl = lockedLocks.get().get(canonf);
        if (cl != null) {
            if (cl.decLockCount() > 0) {
                return;
            }
        }
        try {
            lockedLocks.get().remove(canonf);
            if (lock == globalLock) {
                int mod = localModificationCount;
                localModificationCount = 0;
                if (mod != 0 && noFsMod == true) {
                    long gmod = getModificationCounter();
                    setModificationCounter(gmod + mod);
                }
            }
        } finally {
            lock.releaseLock();
            // if (lock != globalLock) {
            lock.closeLock();
            // }
        }
    }

    /**
     * Gets the lock.
     *
     * @param name the name
     * @return the lock
     */
    protected StdFileSystemLock getLock(String name) {
        if (name == null) {
            return getGlobalLock();
        }
        File f = new File(rootFile, name);
        ensureFileInFs(f);
        StdFileSystemLock lock = new StdFileSystemLock(f);
        return lock;
    }

    /**
     * Gets the lock.
     *
     * @param name the name
     * @param timeOutMs the time out ms
     * @return the lock
     */
    protected StdFileSystemLock getLock(String name, long timeOutMs) {
        if (timeOutMs == -1) {
            timeOutMs = TimeInMillis.MINUTE * 20;
        }
        StdFileSystemLock lock;
        String nomName = name;
        if (name == null) {
            nomName = globalLockFileName;

        }
        File f = new File(rootFile, nomName);
        ensureFileInFs(f);
        String fqName = canonPath(f);
        lock = lockedLocks.get().get(fqName);

        if (lock != null) {
            lock.incLockCount();
            return lock;
        }
        lock = getLock(name);
        if (lock.aquireLock(timeOutMs) == false) {
            releaseLock(lock, true);
            return null;
        }
        lock.incLockCount();
        lockedLocks.get().put(fqName, lock);
        return lock;
    }

    /**
     * Run in transaction internal.
     *
     * @param <R> the generic type
     * @param lockFile the lock file
     * @param timeOutMs the time out ms
     * @param callback the callback
     * @param noFsMod the no fs mod
     * @return the r
     */
    protected <R> R runInTransactionInternal(String lockFile, long timeOutMs,
            CallableX<R, RuntimeException> callback, boolean noFsMod) {
        StdFileSystemLock lock = getLock(lockFile, timeOutMs);
        if (lock == null) {
            throw new FsFileLockException("Cannot lock file " + (lockFile == null ? globalLockFileName : lockFile)
                    + " in " + timeOutMs + " ms");
        }
        try {
            return callback.call();
        } finally {
            releaseLock(lock, noFsMod);
        }
    }

    @Override
    public long getModificationCounter() {
        Long ret = runInTransaction(null, 10000L, false, new CallableX<Long, RuntimeException>() {
            @Override
            public Long call() throws RuntimeException {
                try {
                    int bufSize = 1000;
                    ByteBuffer buffer = ByteBuffer.allocate(bufSize);
                    int readed = globalLock.getChannel().read(buffer, 0);
                    if (readed == 0 || readed == -1) {
                        return 0L;
                    }
                    byte[] data = buffer.array();
                    try {
                        return Long.parseLong(new String(data, 0, readed), 10);
                    } catch (NumberFormatException ex) {
                        return 0L;
                    }
                } catch (IOException ex) {
                    return 0L;
                }
            }
        });
        return ret;
    }

    public void setModificationCounter(long nc) {
        byte[] data = Long.toString(nc).getBytes();
        try {
            globalLock.openLock();
            globalLock.getChannel().write(ByteBuffer.wrap(data), 0);
            globalLock.getChannel().force(true);
        } catch (IOException ex) {
            throw new RuntimeIOException(ex);
        }
    }

    @Override
    public <R> R runInTransaction(String lockFile, long timeOut, boolean noFsMod,
            CallableX<R, RuntimeException> callback) {
        if (localSync == true) {
            synchronized (this) {
                return runInTransactionInternal(lockFile, timeOut, callback, noFsMod);
            }
        }
        return runInTransactionInternal(lockFile, timeOut, callback, noFsMod);
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
        this.canonRoot = canonPath(new File(root));
        this.rootFile = new File(canonRoot);
        if (rootFile.exists() == false) {
            boolean success = rootFile.mkdirs();
            if (success == false) {
                // strange behavoir on Windows. first call return false, but after a sleep return true;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    // ignore it.
                }
                success = rootFile.mkdirs();
                if (success == false) {
                    throw new FsException("Cannot create FileSystem root directory: " + canonRoot);
                }
            }
        }
    }

    public boolean isLocalSync() {
        return localSync;
    }

    public void setLocalSync(boolean localSync) {
        this.localSync = localSync;
    }

    public String getGlobalLockFileName() {
        return globalLockFileName;
    }

    public void setGlobalLockFileName(String globalLockFileName) {
        this.globalLockFileName = globalLockFileName;
    }

    public int getLocalModificationCount() {
        return localModificationCount;
    }

    public void setLocalModificationCount(int modificationCount) {
        this.localModificationCount = modificationCount;
    }

}