ome.services.scripts.ScriptRepoHelper.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.scripts.ScriptRepoHelper.java

Source

/*
 *   $Id$
 *
 *   Copyright 2010 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services.scripts;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import ome.conditions.InternalException;
import ome.conditions.RemovedSessionException;
import ome.model.core.OriginalFile;
import ome.model.meta.ExperimenterGroup;
import ome.services.util.Executor;
import ome.system.Principal;
import ome.system.Roles;
import ome.system.ServiceFactory;
import ome.util.SqlAction;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.CanReadFileFilter;
import org.apache.commons.io.filefilter.EmptyFileFilter;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.type.StringType;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.transaction.annotation.Transactional;

/**
 * Strategy used by the ScriptRepository for registering, loading, and saving
 * files.
 *
 * @since Beta4.2
 */
public class ScriptRepoHelper {

    /**
     * Id used by all script repositories. Having a well defined string allows
     * for various repositories to all provide the same functionality.
     */
    public final static String SCRIPT_REPO = "ScriptRepo";

    /**
     * {@link IOFileFilter} instance used during {@link #iterate()} to find the
     * matching scripts in the given directory.
     */
    public final static IOFileFilter SCRIPT_FILTER = new AndFileFilter(
            Arrays.asList(new FileFilter[] { EmptyFileFilter.NOT_EMPTY, HiddenFileFilter.VISIBLE,
                    CanReadFileFilter.CAN_READ, new WildcardFileFilter("*.py") }));

    private final String uuid;

    private final File dir;

    private final Executor ex;

    private final Principal p;

    private final Roles roles;

    protected final Log log = LogFactory.getLog(getClass());

    /**
     * @see #ScriptRepoHelper(String, File, Executor, Principal)
     */
    public ScriptRepoHelper(Executor ex, String sessionUuid, Roles roles) {
        this(new File(getDefaultScriptDir()), ex, new Principal(sessionUuid), roles);
        try {
            loadAll(true);
        } catch (RemovedSessionException rse) {
            log.error("Script failure!!! RemovedSession on startup: are we testing?");
        }
    }

    /**
     * @see #ScriptRepoHelper(String, File, Executor, Principal)
     */
    public ScriptRepoHelper(File dir, Executor ex, Principal p, Roles roles) {
        this(SCRIPT_REPO, dir, ex, p, roles);
    }

    /**
     *
     * @param uuid
     *            Allows setting a non-default uuid for this script service.
     *            Primarily for testing, since services rely on the repository
     *            name for finding one another.
     * @param dir
     *            The directory used by the repo as its root. Other constructors
     *            use {@link #getDefaultScriptDir()} internally.
     * @param ex
     * @param p
     */
    public ScriptRepoHelper(String uuid, File dir, Executor ex, Principal p, Roles roles) {
        this.roles = roles;
        this.uuid = uuid;
        this.dir = dir;
        this.ex = ex;
        this.p = p;
        if (dir == null) {
            throw new InternalException("Null dir!");
        }
        if (!dir.exists()) {
            throw new InternalException("Does not exist: " + dir.getAbsolutePath());
        }
        if (!dir.canRead()) {
            throw new InternalException("Cannot read: " + dir.getAbsolutePath());
        }
    }

    /**
     * Directory which will be used as the root of this repository if no
     * directory is passed to a constructor. Equivalent to "lib/scripts" from
     * the current directory.
     */
    public static String getDefaultScriptDir() {
        File current = new File(".");
        File lib = new File(current, "lib");
        File scripts = new File(lib, "scripts");
        return scripts.getAbsolutePath();
    }

    /**
     * Returns the actual root of this repository.
     *
     * @see #getDefaultScriptDir()
     */
    public String getScriptDir() {
        return dir.getAbsolutePath();
    }

    /**
     * Uuid of this repository. In the normal case, this will equal
     * {@link #SCRIPT_REPO}.
     */
    public String getUuid() {
        return uuid;
    }

    /**
     * Returns the number of files which match {@link #SCRIPT_FILTER} in
     * {@link #dir}. Uses {@link #iterate()} internally.
     */
    public int countOnDisk() {
        int size = 0;
        Iterator<File> it = iterate();
        while (it.hasNext()) {
            File f = it.next();
            if (f.canRead() && f.isFile() && !f.isHidden()) {
                size++;
            }
        }
        return size;
    }

    public int countInDb() {
        return (Integer) ex.executeSql(new Executor.SimpleSqlWork(this, "countInDb") {
            @Transactional(readOnly = true)
            public Object doWork(SqlAction sql) {
                return countInDb(sql);
            }
        });
    }

    public int countInDb(SqlAction sql) {
        return sql.repoScriptCount(uuid);
    }

    @SuppressWarnings("unchecked")
    public List<Long> idsInDb() {
        return (List<Long>) ex.executeSql(new Executor.SimpleSqlWork(this, "idsInDb") {
            @Transactional(readOnly = true)
            public Object doWork(SqlAction sql) {
                return idsInDb(sql);
            }
        });
    }

    public List<Long> idsInDb(SqlAction sql) {
        try {
            return sql.fileIdsInDb(uuid);
        } catch (EmptyResultDataAccessException e) {
            return Collections.emptyList();
        }
    }

    public boolean isInRepo(final long id) {
        return (Boolean) ex.executeSql(new Executor.SimpleSqlWork(this, "isInRepo", id) {
            @Transactional(readOnly = true)
            public Object doWork(SqlAction sql) {
                return isInRepo(sql, id);
            }
        });
    }

    public boolean isInRepo(SqlAction sql, final long id) {
        try {
            int count = sql.isFileInRepo(uuid, id);
            return count > 0;
        } catch (EmptyResultDataAccessException e) {
            return false;
        }
    }

    public Long findInDb(final String path, final boolean scriptsOnly) {
        RepoFile repoFile = new RepoFile(dir, path);
        return findInDb(repoFile, scriptsOnly);
    }

    public Long findInDb(final RepoFile file, final boolean scriptsOnly) {
        return (Long) ex.executeSql(new Executor.SimpleSqlWork(this, "findInDb", file, scriptsOnly) {
            @Transactional(readOnly = true)
            public Object doWork(SqlAction sql) {
                return findInDb(sql, file, scriptsOnly);
            }
        });
    }

    /**
     * Looks to see if a path is contained in the repository.
     */
    public Long findInDb(SqlAction sql, RepoFile repoFile, boolean scriptsOnly) {
        try {
            return sql.findRepoFile(uuid, repoFile.dirname(), repoFile.basename(),
                    scriptsOnly ? "text/x-python" : null);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public Iterator<File> iterate() {
        return FileUtils.iterateFiles(dir, SCRIPT_FILTER, TrueFileFilter.TRUE);
    }

    /**
     * Walks all files in the repository (via {@link #iterate()} and adds them
     * if not found in the database.
     *
     * If modificationCheck is true, then a change in the sha1 for a file in
     * the repository will cause the old file to be removed from the repository
     * <pre>(uuid == null)</pre> and a new file created in its place.
     *
     * @param modificationCheck
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<OriginalFile> loadAll(final boolean modificationCheck) {
        final Iterator<File> it = iterate();
        final List<OriginalFile> rv = new ArrayList<OriginalFile>();
        return (List<OriginalFile>) ex.execute(p, new Executor.SimpleWork(this, "loadAll", modificationCheck) {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {

                SqlAction sqlAction = getSqlAction();

                File f = null;
                RepoFile file = null;

                while (it.hasNext()) {
                    f = it.next();
                    file = new RepoFile(dir, f);
                    Long id = findInDb(sqlAction, file, false); // non-scripts count
                    String sha1 = null;
                    OriginalFile ofile = null;
                    if (id == null) {
                        ofile = addOrReplace(sqlAction, sf, file, null);
                    } else {

                        ofile = load(id, session, getSqlAction(), true); // checks for type & repo
                        if (ofile == null) {
                            continue; // wrong type or similar
                        }

                        if (modificationCheck) {
                            sha1 = file.sha1();
                            if (!sha1.equals(ofile.getSha1())) {
                                ofile = addOrReplace(sqlAction, sf, file, id);
                            }
                        }
                    }
                    rv.add(ofile);
                }
                removeMissingFilesFromDb(sqlAction, session, rv);
                return rv;
            }
        });
    }

    /**
     *
     * @param repoFile
     * @param old
     * @return
     */
    public OriginalFile addOrReplace(final RepoFile repoFile, final Long old) {
        return (OriginalFile) ex.execute(p, new Executor.SimpleWork(this, "addOrReplace", repoFile, old) {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {
                return addOrReplace(getSqlAction(), sf, repoFile, old);
            }
        });
    }

    protected OriginalFile addOrReplace(SqlAction sqlAction, ServiceFactory sf, final RepoFile repoFile,
            final Long old) {

        if (old != null) {
            unregister(old, sqlAction);
            log.info("Unregistered " + old);
        }

        OriginalFile ofile = new OriginalFile();
        return update(repoFile, sqlAction, sf, ofile);
    }

    /**
     * Given the current files on disk, {@link #unregister(Long, Session)}
     * all files which have been removed from disk.
     */
    public long removeMissingFilesFromDb(SqlAction sqlAction, Session session, List<OriginalFile> filesOnDisk) {
        List<Long> idsInDb = idsInDb(sqlAction);
        if (idsInDb.size() != filesOnDisk.size()) {
            log.info(String.format("Script missing from disk: %s in db, %s on disk!", idsInDb.size(),
                    filesOnDisk.size()));
        }

        Set<Long> setInDb = new HashSet<Long>();
        Set<Long> setOnDisk = new HashSet<Long>();

        setInDb.addAll(idsInDb);
        for (OriginalFile f : filesOnDisk) {
            setOnDisk.add(f.getId());
        }

        // Now contains only those which are missing
        setInDb.removeAll(setOnDisk);

        for (Long l : setInDb) {
            unregister(l, sqlAction);
        }

        return setInDb.size();
    }

    /**
     * Unregisters a given file from the script repository by setting its
     * Repo uuid to null.
     */
    protected void unregister(final Long old, SqlAction sqlAction) {
        sqlAction.setFileRepo(old, null);
    }

    public OriginalFile update(final RepoFile repoFile, final Long id) {
        return (OriginalFile) ex.execute(p, new Executor.SimpleWork(this, "update", repoFile, id) {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {
                OriginalFile ofile = load(id, session, getSqlAction(), true);
                return update(repoFile, getSqlAction(), sf, ofile);
            }
        });
    }

    private OriginalFile update(final RepoFile repoFile, SqlAction sqlAction, ServiceFactory sf,
            OriginalFile ofile) {
        ofile.setPath(repoFile.dirname());
        ofile.setName(repoFile.basename());
        ofile.setSha1(repoFile.sha1());
        ofile.setSize(repoFile.length());
        ofile.setMimetype("text/x-python");
        ofile.getDetails().setGroup(new ExperimenterGroup(roles.getUserGroupId(), false));
        ofile = sf.getUpdateService().saveAndReturnObject(ofile);

        sqlAction.setFileRepo(ofile.getId(), uuid);

        return ofile;
    }

    public String read(String path) throws IOException {
        final RepoFile repo = new RepoFile(dir, path);
        return FileUtils.readFileToString(repo.file());
    }

    public RepoFile write(String path, String text) throws IOException {
        RepoFile repo = new RepoFile(dir, path);
        return write(repo, text);
    }

    public RepoFile write(RepoFile repo, String text) throws IOException {
        FileUtils.writeStringToFile(repo.file(), text); // truncates itself. ticket:2337
        return repo;
    }

    public OriginalFile load(final long id, final boolean check) {
        return (OriginalFile) ex.execute(p, new Executor.SimpleWork(this, "load", id) {
            @Transactional(readOnly = true)
            public Object doWork(Session session, ServiceFactory sf) {
                return load(id, session, getSqlAction(), check);
            }
        });
    }

    public OriginalFile load(final long id, Session s, SqlAction sqlAction, boolean check) {
        if (check) {
            String repo = sqlAction.scriptRepo(id);
            if (!uuid.equals(repo)) {
                return null;
            }
        }
        return (OriginalFile) s.get(OriginalFile.class, id);
    }

    /**
     * Checks if
     */
    public void modificationCheck() {
        loadAll(true);
    }

    public boolean delete(long id) {

        final OriginalFile file = load(id, true);
        if (file == null) {
            return false;
        }

        ex.execute(p, new Executor.SimpleWork(this, "delete", id) {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {
                sf.getUpdateService().deleteObject(file);
                return null;
            }
        });

        FileUtils.deleteQuietly(new File(dir, file.getPath()));

        return true;
    }

}