org.ngrinder.script.service.FileEntryService.java Source code

Java tutorial

Introduction

Here is the source code for org.ngrinder.script.service.FileEntryService.java

Source

/* 
 * 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 org.ngrinder.script.service;

import static org.ngrinder.common.util.NoOp.noOp;
import static org.ngrinder.common.util.Preconditions.checkNotEmpty;
import static org.ngrinder.common.util.Preconditions.checkNotNull;
import static org.apache.commons.lang.StringUtils.startsWithIgnoreCase;
import java.io.File;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.ngrinder.common.exception.NGrinderRuntimeException;
import org.ngrinder.common.util.HttpContainerContext;
import org.ngrinder.infra.config.Config;
import org.ngrinder.model.User;
import org.ngrinder.script.model.FileEntry;
import org.ngrinder.script.model.FileType;
import org.ngrinder.script.repository.FileEntryRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.io.fs.FSHook;
import org.tmatesoft.svn.core.internal.io.fs.FSHookEvent;
import org.tmatesoft.svn.core.internal.io.fs.FSHooks;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;

/**
 * File entry service class.<br/>
 * 
 * This class is responsible for creating user svn repository whenever a user is created and connect
 * the user to the underlying svn.
 * 
 * @author JunHo Yoon
 * @since 3.0
 */
@Service
public class FileEntryService {

    private static final Logger LOG = LoggerFactory.getLogger(FileEntryService.class);

    private SVNClientManager svnClientManager = SVNClientManager.newInstance();

    @Autowired
    private Config config;

    @Autowired
    private HttpContainerContext httpContainerContext;

    @Autowired
    @Qualifier("cacheManager")
    private CacheManager cacheManager;

    @Autowired
    private FileEntryRepository fileEntityRepository;

    /**
     * Initialize {@link FileEntryService}.
     */
    @PostConstruct
    public void init() {
        // Add cache invalidation hook.
        FSHooks.registerHook(new FSHook() {
            @Override
            public void onHook(FSHookEvent event) throws SVNException {
                if (event.getType().equals(FSHooks.SVN_REPOS_HOOK_POST_COMMIT)) {
                    String name = event.getReposRootDir().getName();
                    invalidateCache(name);
                }
            }
        });

    }

    /**
     * invalidate the file_entry_seach_cache.
     * 
     * @param userId
     *            userId.
     */
    public void invalidateCache(String userId) {
        cacheManager.getCache("file_entry_search_cache").evict(userId);
    }

    /**
     * Create user svn repo.
     * 
     * This method is executed async way.
     * 
     * @param user
     *            newly created user.
     */
    @Async
    public void prepare(User user) {
        File newUserDirectory = getUserRepoDirectory(user);
        try {
            if (!newUserDirectory.exists()) {
                createUserRepo(user, newUserDirectory);
            }
        } catch (SVNException e) {
            LOG.error("Error while prepare user {}'s repo", user.getUserName(), e);
        }
    }

    private SVNURL createUserRepo(User user, File newUserDirectory) throws SVNException {
        return svnClientManager.getAdminClient().doCreateRepository(newUserDirectory, user.getUserId(), true, true);
    }

    private File getUserRepoDirectory(User user) {
        return new File(config.getHome().getRepoDirectoryRoot(), checkNotNull(user.getUserId()));
    }

    /**
     * Get all {@link FileEntry} for the given user. This method is subject to be cached because it
     * takes time.
     * 
     * @param user
     *            user
     * @return cached {@link FileEntry} list
     */
    @Cacheable(value = "file_entry_search_cache", key = "#user.userId")
    public List<FileEntry> getAllFileEntries(User user) {
        return fileEntityRepository.findAll(user);
    }

    /**
     * Get file entries from underlying svn for given path.
     * 
     * @param user
     *            the user
     * @param path
     *            path in the repo
     * @param revision
     *            revision number. -1 if HEAD.
     * @return file entry list
     */
    public List<FileEntry> getFileEntries(User user, String path, Long revision) {
        // If it's not created, make one.
        prepare(user);
        return fileEntityRepository.findAll(user, path, revision);
    }

    /**
     * Get single file entity.
     * 
     * The return value has content byte.
     * 
     * @param user
     *            the user
     * @param path
     *            path in the svn repo
     * @param revision
     *            file revision.
     * @return single file entity
     */
    public FileEntry getFileEntry(User user, String path, long revision) {
        return fileEntityRepository.findOne(user, path, SVNRevision.create(revision));
    }

    /**
     * Get single file entity.
     * 
     * The return value has content byte.
     * 
     * @param user
     *            the user
     * @param path
     *            path in the svn repo
     * @return single file entity
     */
    public FileEntry getFileEntry(User user, String path) {
        return fileEntityRepository.findOne(user, path, SVNRevision.HEAD);
    }

    /**
     * Check file existence.
     * 
     * @param user
     *            user
     * @param path
     *            path in user repo
     * @return true if exists.
     */
    public boolean hasFileEntry(User user, String path) {
        return fileEntityRepository.hasFileEntry(user, path);
    }

    /**
     * Add folder on the given path.
     * 
     * @param user
     *            user
     * @param path
     *            base path
     * @param comment
     *            comment
     * @param folderName
     *            folder name
     */
    public void addFolder(User user, String path, String folderName, String comment) {
        FileEntry entry = new FileEntry();
        entry.setPath(path + "/" + folderName);
        entry.setFileType(FileType.DIR);
        entry.setDescription(comment);
        fileEntityRepository.save(user, entry, null);
    }

    /**
     * Get file entity for the given revision.
     * 
     * @param user
     *            the user
     * @param path
     *            path in the repo
     * @param revision
     *            revision. if -1, HEAD
     * @return file entity
     */
    public FileEntry getFileEntry(User user, String path, Long revision) {
        SVNRevision svnRev = (revision == null || revision == -1) ? SVNRevision.HEAD : SVNRevision.create(revision);
        return fileEntityRepository.findOne(user, path, svnRev);
    }

    /**
     * Save File entry.
     * 
     * @param user
     *            the user
     * @param fileEntity
     *            fileEntity to be saved
     */
    public void save(User user, FileEntry fileEntity) {
        prepare(user);
        checkNotEmpty(fileEntity.getPath());
        fileEntityRepository.save(user, fileEntity, fileEntity.getEncoding());
    }

    /**
     * Delete file entries.
     * 
     * @param user
     *            the user
     * @param path
     *            the base path
     * @param files
     *            files under base path
     */
    public void delete(User user, String path, String[] files) {
        List<String> fileList = new ArrayList<String>();
        for (String each : files) {
            fileList.add(path + "/" + each);
        }
        fileEntityRepository.delete(user, fileList.toArray(new String[] {}));
    }

    String getTestNameFromUrl(String urlString) {
        URL url;
        try {

            url = new URL(urlString);
            String urlPath = "/".equals(url.getPath()) ? "" : url.getPath();
            return (url.getHost() + urlPath).replaceAll("[\\&\\?\\%\\-]", "_");

        } catch (MalformedURLException e) {
            throw new NGrinderRuntimeException("Error while translating " + urlString, e);
        }
    }

    /**
     * Create new FileEntry.
     * 
     * @param user
     *            user
     * @param path
     *            path
     * @param fileName
     *            fileName
     * @param url
     *            url
     * @return created file entry
     */
    public FileEntry prepareNewEntry(User user, String path, String fileName, String url) {
        FileEntry fileEntry = new FileEntry();
        String filePath;
        if (!StringUtils.isBlank(path)) {
            filePath = path + "/" + fileName;
        } else {
            filePath = fileName;
        }
        fileEntry.setPath(filePath);
        fileEntry.setContent(loadFreeMarkerTemplate(user, url));
        addHostProperties(fileEntry, url);
        return fileEntry;
    }

    private void addHostProperties(FileEntry fileEntry, String url) {
        Map<String, String> map = new HashMap<String, String>();
        String host;
        try {
            host = new URL(url).getHost();
            map.put("targetHosts", StringUtils.trim(host));
            fileEntry.setProperties(map);
        } catch (MalformedURLException e) {
            // FALL THROUGH
            noOp();
        }
    }

    /**
     * Create new FileEntry for the url.
     * 
     * @param user
     *            user
     * @param urlString
     *            url to be tested.
     * @return created new {@link FileEntry}
     */
    public FileEntry prepareNewEntryForQuickTest(User user, String urlString) {
        String testNameFromUrl = getTestNameFromUrl(urlString);
        // addFolder(user, "", testNameFromUrl);
        // There might be race condition here... What if a user changes the SVN
        // repo while saving
        // newEntry??
        FileEntry newEntry = prepareNewEntry(user, testNameFromUrl, "script.py", urlString);
        newEntry.setDescription("Quick test for " + urlString);
        save(user, newEntry);
        return getFileEntry(user, testNameFromUrl + "/" + "script.py");
    }

    /**
     * Load freemarker template for quick test.
     * 
     * @param user
     *            user
     * @param url
     *            url
     * @return generated test script
     */
    public String loadFreeMarkerTemplate(User user, String url) {

        try {
            Configuration freemarkerConfig = new Configuration();
            ClassPathResource cpr = new ClassPathResource("script_template");
            freemarkerConfig.setDirectoryForTemplateLoading(cpr.getFile());
            freemarkerConfig.setObjectWrapper(new DefaultObjectWrapper());
            Template template = freemarkerConfig.getTemplate("basic_template.ftl");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("url", url);
            map.put("user", user);
            StringWriter writer = new StringWriter();
            template.process(map, writer);
            return writer.toString();
        } catch (Exception e) {
            LOG.error("Error while fetching template for quick start", e);
        }
        return "";
    }

    /**
     * Get SVN URL for the given user and the given subpath.
     * 
     * @param user
     *            user
     * @param path
     *            subpath
     * @return SVN URL
     */
    public String getSvnUrl(User user, String path) {
        String contextPath = httpContainerContext.getCurrentRequestUrlFromUserRequest();
        StringBuilder url = new StringBuilder(config.getSystemProperties().getProperty("http.url", contextPath));
        url.append("/svn/").append(user.getUserId());
        if (StringUtils.isNotEmpty(path)) {
            url.append("/").append(path.trim());
        }
        return url.toString();
    }

    /**
     * Get a SVN content into given dir.
     * 
     * @param user
     *            user
     * @param fromPath
     *            path in svn subpath
     * @param toDir
     *            to directory
     */
    public void writeContentTo(User user, String fromPath, File toDir) {
        fileEntityRepository.writeContentTo(user, fromPath, toDir);
    }

    /**
     * Get Lib and Resources. This method will collect the files of lib and resources folder on the
     * same folder whre script is located.
     * 
     * @param user
     *            user
     * @param scriptPath
     *            path of script
     * @param revision
     *            revision number. If head, it should be -1.
     * @return {@link FileEntry} list
     */
    public List<FileEntry> getLibAndResourcesEntries(User user, String scriptPath, Long revision) {
        String path = FilenameUtils.getPath(scriptPath);
        List<FileEntry> fileList = new ArrayList<FileEntry>();
        List<FileEntry> fileEntries = getFileEntries(user, path + "lib/", revision);
        for (FileEntry eachFileEntry : fileEntries) {
            // Skip jython 2.5... it's already included.
            if (startsWithIgnoreCase(eachFileEntry.getFileName(), "jython-2.5.")
                    || startsWithIgnoreCase(eachFileEntry.getFileName(), "jython-standalone-2.5.")) {
                continue;
            }
            FileType fileType = eachFileEntry.getFileType();
            if (fileType.isLibDistribtable()) {
                fileList.add(eachFileEntry);
            }
        }
        fileEntries = getFileEntries(user, path + "resources/", revision);
        for (FileEntry eachFileEntry : fileEntries) {

            FileType fileType = eachFileEntry.getFileType();
            if (fileType.isResourceDistributable()) {
                fileList.add(eachFileEntry);
            }
        }
        return fileList;
    }
}