Package org.ngrinder.script.repository

Source Code of org.ngrinder.script.repository.FileEntryRepository

/*
* 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.repository;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang.StringUtils;
import org.ngrinder.common.exception.NGrinderRuntimeException;
import org.ngrinder.common.model.Home;
import org.ngrinder.common.util.EncodingUtils;
import org.ngrinder.infra.config.Config;
import org.ngrinder.model.User;
import org.ngrinder.script.model.FileCategory;
import org.ngrinder.script.model.FileEntry;
import org.ngrinder.script.model.FileType;
import org.ngrinder.user.repository.UserRepository;
import org.ngrinder.user.service.UserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Map.Entry;

import static org.ngrinder.common.util.CollectionUtils.newArrayList;
import static org.ngrinder.common.util.ExceptionUtils.processException;
import static org.ngrinder.common.util.NoOp.noOp;
import static org.ngrinder.common.util.Preconditions.checkNotNull;

/**
* SVN FileEntity repository.
*
* This class save and retrieve {@link FileEntry} from Local SVN folders.
*
* @author JunHo Yoon
* @since 3.0
*/
@Profile("production")
@Component
public class FileEntryRepository {

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

  @Autowired
  private Config config;

  private Home home;

  private File subversionHome;

  /**
   * Initialize the {@link FileEntryRepository}. This method should be
   * performed to set up FS Repository.
   */
  @PostConstruct
  public void init() {
    FSRepositoryFactory.setup();
    home = config.getHome();
    subversionHome = home.getSubFile("subversion");
  }

  @Autowired
  private UserRepository userRepository;

  /**
   * Get user repository.
   *
   * For unit test, This can be overridable.
   *
   * @param user the user
   * @return user repository path.
   */
  public File getUserRepoDirectory(User user) {
    return home.getUserRepoDirectory(user.getUserId());
  }

  /**
   * Return all {@link FileEntry}s under the given path.
   *
   * @param user     user
   * @param path     path under which files are searched.
   * @param revision . null if head.
   * @return found {@link FileEntry}s
   */
  public List<FileEntry> findAll(User user, final String path, Long revision) {
    return findAll(user, path, revision, false);
  }

  /**
   * Return all {@link FileEntry}s under the given path.
   *
   * @param user      user
   * @param path      path under which files are searched.
   * @param revision  null if head.
   * @param recursive true if recursive finding
   * @return found {@link FileEntry}s
   */
  public List<FileEntry> findAll(User user, final String path, Long revision, boolean recursive) {
    SVNRevision svnRevision = SVNRevision.HEAD;
    if (revision != null && -1L != revision) {
      svnRevision = SVNRevision.create(revision);
    }
    final List<FileEntry> fileEntries = newArrayList();
    SVNClientManager svnClientManager = getSVNClientManager();
    try {
      svnClientManager.getLogClient().doList(SVNURL.fromFile(getUserRepoDirectory(user)).appendPath(path, true),
          svnRevision, svnRevision, true, recursive, new ISVNDirEntryHandler() {
        @Override
        public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {

          FileEntry script = new FileEntry();
          // Exclude base path "/"
          if (StringUtils.isBlank(dirEntry.getRelativePath())) {
            return;
          }
          script.setPath(FilenameUtils.normalize(path + "/" + dirEntry.getRelativePath(), true));
          script.setCreatedDate(dirEntry.getDate());
          script.setLastModifiedDate(dirEntry.getDate());
          script.setDescription(dirEntry.getCommitMessage());
          script.setRevision(dirEntry.getRevision());
          if (dirEntry.getKind() == SVNNodeKind.DIR) {
            script.setFileType(FileType.DIR);
          } else {
            script.getFileType();
            script.setFileSize(dirEntry.getSize());
          }
          fileEntries.add(script);
        }
      });
    } catch (Exception e) {
      LOG.debug("findAll() to the not existing folder {}", path);
    } finally {
      closeSVNClientManagerQuietly(svnClientManager);
    }
    return fileEntries;
  }

  /**
   * Return all {@link FileEntry}s which user have. It excludes
   * {@link FileType#DIR} entries.
   *
   * @param user user
   * @return found {@link FileEntry}s
   */
  public List<FileEntry> findAll(final User user) {
    final List<FileEntry> scripts = newArrayList();
    SVNClientManager svnClientManager = getSVNClientManager();
    try {
      svnClientManager.getLogClient().doList(SVNURL.fromFile(getUserRepoDirectory(user)), SVNRevision.HEAD,
          SVNRevision.HEAD, false, true, new ISVNDirEntryHandler() {
        @Override
        public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {
          FileEntry script = new FileEntry();
          String relativePath = dirEntry.getRelativePath();
          if (StringUtils.isBlank(relativePath)) {
            return;
          }
          script.setCreatedDate(dirEntry.getDate());
          script.setLastModifiedDate(dirEntry.getDate());
          script.setPath(relativePath);
          script.setDescription(dirEntry.getCommitMessage());
          long reversion = dirEntry.getRevision();
          script.setRevision(reversion);
          script.setFileType(dirEntry.getKind() == SVNNodeKind.DIR ? FileType.DIR : null);
          script.setFileSize(dirEntry.getSize());
          scripts.add(script);
        }
      });
    } catch (Exception e) {
      LOG.error("Error while fetching files from SVN for {}", user.getUserId());
      LOG.debug("Error details :", e);
      throw new NGrinderRuntimeException(e);
    } finally {
      closeSVNClientManagerQuietly(svnClientManager);
    }
    return scripts;

  }

  /**
   * Return a {@link FileEntry} for the given path and revision.
   *
   * @param user     user
   * @param path     path in the svn repo
   * @param revision revision of the file
   * @return found {@link FileEntry}, null if not found
   */
  public FileEntry findOne(User user, String path, SVNRevision revision) {
    final FileEntry script = new FileEntry();
    SVNClientManager svnClientManager = null;
    ByteArrayOutputStream outputStream = null;
    try {
      svnClientManager = getSVNClientManager();

      SVNURL userRepoUrl = SVNURL.fromFile(getUserRepoDirectory(user));
      if (userRepoUrl == null) {
        return null;
      }
      SVNRepository repo = svnClientManager.createRepository(userRepoUrl, true);
      SVNNodeKind nodeKind = repo.checkPath(path, -1);
      if (nodeKind == SVNNodeKind.NONE) {
        return null;
      }
      outputStream = new ByteArrayOutputStream();
      SVNProperties fileProperty = new SVNProperties();
      // Get File.
      repo.getFile(path, revision.getNumber(), fileProperty, outputStream);
      SVNDirEntry lastRevisionedEntry = repo.info(path, -1);
      long lastRevisionNumber = (lastRevisionedEntry == null) ? -1 : lastRevisionedEntry.getRevision();
      String revisionStr = fileProperty.getStringValue(SVNProperty.REVISION);
      long revisionNumber = Long.parseLong(revisionStr);
      SVNDirEntry info = repo.info(path, revisionNumber);
      byte[] byteArray = outputStream.toByteArray();
      script.setPath(path);
      for (String name : fileProperty.nameSet()) {
        script.getProperties().put(name, fileProperty.getStringValue(name));
      }
      script.setFileType(FileType.getFileTypeByExtension(FilenameUtils.getExtension(script.getFileName())));
      if (script.getFileType().isEditable()) {
        String autoDetectedEncoding = EncodingUtils.detectEncoding(byteArray, "UTF-8");
        script.setContent(new String(byteArray, autoDetectedEncoding));
        script.setEncoding(autoDetectedEncoding);
        script.setContentBytes(byteArray);
      } else {
        script.setContentBytes(byteArray);
      }
      script.setDescription(info.getCommitMessage());
      script.setRevision(revisionNumber);
      script.setLastRevision(lastRevisionNumber);
      script.setCreatedUser(user);
    } catch (Exception e) {
      LOG.error("Error while fetching a file from SVN {}", user.getUserId() + "_" + path, e);
      return null;
    } finally {
      closeSVNClientManagerQuietly(svnClientManager);
      IOUtils.closeQuietly(outputStream);
    }
    return script;
  }

  private void addPropertyValue(ISVNEditor editor, FileEntry fileEntry) throws SVNException {
    if (fileEntry.getFileType().getFileCategory() == FileCategory.SCRIPT) {
      editor.changeFileProperty(fileEntry.getPath(), "targetHosts", SVNPropertyValue.create(""));
    }
    for (Entry<String, String> each : fileEntry.getProperties().entrySet()) {
      editor.changeFileProperty(fileEntry.getPath(), each.getKey(), SVNPropertyValue.create(each.getValue()));
    }
  }

  /**
   * Save fileEntry on the {@link FileEntry.getPath()} location.
   *
   * @param user      the user
   * @param fileEntry fileEntry to be saved
   * @param encoding  file encoding with which fileEntry is saved. It is meaningful
   *                  only FileEntry is editable.
   */
  public void save(User user, FileEntry fileEntry, String encoding) {
    SVNClientManager svnClientManager = null;
    ISVNEditor editor = null;
    String checksum = null;
    InputStream bais = null;
    try {
      svnClientManager = getSVNClientManager();
      SVNRepository repo = svnClientManager.createRepository(SVNURL.fromFile(getUserRepoDirectory(user)), true);
      SVNDirEntry dirEntry = repo.info(fileEntry.getPath(), -1);

      // Add base paths
      String fullPath = "";
      // Check.. first
      for (String each : getPathFragment(fileEntry.getPath())) {
        fullPath = fullPath + "/" + each;
        SVNDirEntry folderStepEntry = repo.info(fullPath, -1);
        if (folderStepEntry != null && folderStepEntry.getKind() == SVNNodeKind.FILE) {
          throw processException("User " + user.getUserId() + " tried to create folder "
              + fullPath + ". It's file..");
        }
      }

      editor = repo.getCommitEditor(fileEntry.getDescription(), null, true, null);
      editor.openRoot(-1);
      fullPath = "";
      for (String each : getPathFragment(fileEntry.getPath())) {
        fullPath = fullPath + "/" + each;
        try {
          editor.addDir(fullPath, null, -1);
        } catch (Exception e) {
          // FALL THROUGH
          noOp();
        }
      }

      if (fileEntry.getFileType() == FileType.DIR) {
        editor.addDir(fileEntry.getPath(), null, -1);
      } else {
        if (dirEntry == null) {
          // If it's new file
          editor.addFile(fileEntry.getPath(), null, -1);
        } else {
          // If it's modification
          editor.openFile(fileEntry.getPath(), -1);
        }
        editor.applyTextDelta(fileEntry.getPath(), null);

        // Calc diff
        final SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
        if (fileEntry.getContentBytes() == null && fileEntry.getFileType().isEditable()) {
          bais = new ByteArrayInputStream(checkNotNull(fileEntry.getContent()).getBytes(
              encoding == null ? "UTF-8" : encoding));
        } else {
          bais = new ByteArrayInputStream(fileEntry.getContentBytes());
        }
        checksum = deltaGenerator.sendDelta(fileEntry.getPath(), bais, editor, true);
      }

      addPropertyValue(editor, fileEntry);
      editor.closeFile(fileEntry.getPath(), checksum);
    } catch (Exception e) {
      abortSVNEditorQuietly(editor);
      // If it's adding the folder which already exists... ignore..
      if (e instanceof SVNException && fileEntry.getFileType() == FileType.DIR) {
        if (SVNErrorCode.FS_ALREADY_EXISTS.equals(((SVNException) e).getErrorMessage().getErrorCode())) {
          return;
        }
      }
      LOG.error("Error while saving file to SVN", e);
      throw processException("Error while saving file to SVN", e);
    } finally {
      closeSVNEditorQuietly(editor);
      closeSVNClientManagerQuietly(svnClientManager);
      IOUtils.closeQuietly(bais);
    }
  }

  String[] getPathFragment(String path) {
    String basePath = FilenameUtils.getPath(path);
    return StringUtils.split(FilenameUtils.separatorsToUnix(basePath), "/");

  }

  /**
   * Quietly close svn editor.
   *
   * @param editor editor to be closed.
   */
  private void abortSVNEditorQuietly(ISVNEditor editor) {
    if (editor == null) {
      return;
    }
    try {
      editor.abortEdit();
    } catch (SVNException e) {
      // FALL THROUGH
      noOp();
    }
  }

  /**
   * Quietly close svn editor. This is convenient method.
   *
   * @param editor editor to be closed.
   */
  private void closeSVNEditorQuietly(ISVNEditor editor) {
    if (editor == null) {
      return;
    }
    try {
      // recursively close
      //noinspection InfiniteLoopStatement
      while (true) {
        editor.closeDir();
      }
    } catch (EmptyStackException e) {
      // FALL THROUGH
      noOp();
    } catch (SVNException e) {
      // FALL THROUGH
      noOp();
    } finally {
      try {
        editor.closeEdit();
      } catch (SVNException e) {
        // FALL THROUGH
        noOp();
      }
    }
  }

  /**
   * Delete file entries on given paths. If the one of paths does not exist,
   * all deletion is canceled.
   *
   * @param user  user
   * @param paths paths of file entries.
   */
  public void delete(User user, List<String> paths) {
    SVNClientManager svnClientManager = null;
    ISVNEditor editor = null;
    try {
      svnClientManager = getSVNClientManager();
      SVNRepository repo = svnClientManager.createRepository(SVNURL.fromFile(getUserRepoDirectory(user)), true);

      editor = repo.getCommitEditor("delete", null, true, null);
      editor.openRoot(-1);
      for (String each : paths) {
        editor.deleteEntry(each, -1);
      }
    } catch (Exception e) {
      abortSVNEditorQuietly(editor);
      LOG.error("Error while deleting file from SVN", e);
      throw processException("Error while deleting files from SVN", e);
    } finally {
      closeSVNEditorQuietly(editor);
      closeSVNClientManagerQuietly(svnClientManager);
    }
  }

  @Autowired
  UserContext userContext;

  /**
   * Get svn client manager with the designated subversionHome.
   *
   * @return svn client manager
   */
  public SVNClientManager getSVNClientManager() {
    DefaultSVNOptions options = SVNWCUtil.createDefaultOptions(subversionHome, true);
    ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(subversionHome,
        getCurrentUserId(), null, false);
    return SVNClientManager.newInstance(options, authManager);
  }

  protected String getCurrentUserId() {
    try {
      return userContext.getCurrentUser().getUserId();
    } catch (Exception e) {
      return "default";
    }

  }

  private void closeSVNClientManagerQuietly(SVNClientManager svnClientManager) {
    if (svnClientManager != null) {
      svnClientManager.dispose();
    }
  }

  /**
   * Check file existence.
   *
   * @param user user
   * @param path path in user repo
   * @return true if exists.
   */
  public boolean hasOne(User user, String path) {
    SVNClientManager svnClientManager = null;
    try {
      svnClientManager = getSVNClientManager();
      SVNURL userRepoUrl = SVNURL.fromFile(getUserRepoDirectory(user));
      SVNRepository repo = svnClientManager.createRepository(userRepoUrl, true);
      SVNNodeKind nodeKind = repo.checkPath(path, -1);
      return (nodeKind != SVNNodeKind.NONE);
    } catch (Exception e) {
      LOG.error("Error while fetching files from SVN", e);
      throw processException("Error while checking file existence from SVN", e);
    } finally {
      closeSVNClientManagerQuietly(svnClientManager);
    }
  }

  /**
   * Copy {@link FileEntry} to the given path.
   *
   * This method only work for the file not dir.
   *
   * @param user      user
   * @param path      path of {@link FileEntry}
   * @param toPathDir file dir path to write.
   */
  public void writeContentTo(User user, String path, File toPathDir) {
    SVNClientManager svnClientManager = null;
    FileOutputStream fileOutputStream = null;
    try {
      svnClientManager = getSVNClientManager();

      SVNURL userRepoUrl = SVNURL.fromFile(getUserRepoDirectory(user));
      SVNRepository repo = svnClientManager.createRepository(userRepoUrl, true);
      SVNNodeKind nodeKind = repo.checkPath(path, -1);
      // If it's DIR, it does not work.
      if (nodeKind == SVNNodeKind.NONE || nodeKind == SVNNodeKind.DIR) {
        throw processException("It's not possible to write directory. nodeKind is " + nodeKind);
      }
      //noinspection ResultOfMethodCallIgnored
      toPathDir.mkdirs();
      File destFile = new File(toPathDir, FilenameUtils.getName(path));
      // Prepare parent folders
      fileOutputStream = new FileOutputStream(destFile);
      SVNProperties fileProperty = new SVNProperties();
      // Get file.
      repo.getFile(path, -1L, fileProperty, fileOutputStream);
    } catch (Exception e) {
      LOG.error("Error while fetching files from SVN", e);
      throw processException("Error while fetching files from SVN", e);
    } finally {
      closeSVNClientManagerQuietly(svnClientManager);
      IOUtils.closeQuietly(fileOutputStream);
    }
  }
}
TOP

Related Classes of org.ngrinder.script.repository.FileEntryRepository

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.