SvnResourceService.java

/*
 * Copyright (c) 2002-2021, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.releaser.service.svn;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import fr.paris.lutece.plugins.releaser.business.RepositoryType;
import fr.paris.lutece.plugins.releaser.business.Site;
import fr.paris.lutece.plugins.releaser.business.WorkflowReleaseContext;
import fr.paris.lutece.plugins.releaser.service.ComponentService;
import fr.paris.lutece.plugins.releaser.util.CommandResult;
import fr.paris.lutece.plugins.releaser.util.ConstanteUtils;
import fr.paris.lutece.plugins.releaser.util.IVCSResourceService;
import fr.paris.lutece.plugins.releaser.util.ReleaserUtils;
import fr.paris.lutece.plugins.releaser.util.file.FileUtils;
import fr.paris.lutece.plugins.releaser.util.svn.ReleaseSvnCheckoutClient;
import fr.paris.lutece.plugins.releaser.util.svn.ReleaseSvnCommitClient;
import fr.paris.lutece.plugins.releaser.util.svn.ReleaseSvnCopyClient;
import fr.paris.lutece.plugins.releaser.util.svn.SvnUtils;
import fr.paris.lutece.portal.service.util.AppException;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.util.httpaccess.HttpAccess;
import fr.paris.lutece.util.httpaccess.HttpAccessException;
import fr.paris.lutece.util.signrequest.BasicAuthorizationAuthenticator;
import fr.paris.lutece.util.signrequest.RequestAuthenticator;

// TODO: Auto-generated Javadoc
/**
 * SvnSiteService.
 */
public class SvnResourceService implements IVCSResourceService
{

    /**
     * Fetch the pom.xml content from a repository
     *
     * @param site
     *            the site
     * @param strLogin
     *            the str login
     * @param strPassword
     *            the str password
     * @return The POM content
     */
    public String fetchPom( Site site, String strLogin, String strPassword )
    {

        String strPomUrl = site.getScmUrl( ) + "/pom.xml";
        try
        {
            RequestAuthenticator authenticator = getSiteAuthenticator( strLogin, strPassword );
            HttpAccess httpAccess = new HttpAccess( );
            return httpAccess.doGet( strPomUrl, authenticator, null );
        }
        catch( HttpAccessException ex )
        {

            AppLogService.error( "Error fecthing pom.xml content : " + ex.getMessage( ), ex );
            if ( ex.getResponseCode( ) == 401 )
            {
                throw new AppException( ConstanteUtils.ERROR_TYPE_AUTHENTICATION_ERROR, ex );
            }
        }
        return null;
    }

    /**
     * Gets the last release found in the SVN repository.
     *
     * @param site
     *            the site
     * @param strLogin
     *            the str login
     * @param strPassword
     *            the str password
     * @return The version if found otherwise null
     */
    public String getLastRelease( Site site, String strLogin, String strPassword )
    {

        String strSiteArtifactId = site.getArtifactId( );
        String strTrunkUrl = site.getScmUrl( );
        String strTagsUrl = strTrunkUrl.replace( "trunk", "tags" );

        List<String> list = new ArrayList<>( );

        try
        {
            RequestAuthenticator authenticator = getSiteAuthenticator( strLogin, strPassword );
            HttpAccess httpAccess = new HttpAccess( );
            String strHtml = httpAccess.doGet( strTagsUrl, authenticator, null );
            list = getAnchorsList( strHtml, strSiteArtifactId );
        }
        catch( HttpAccessException e )
        {
            AppLogService.error( "SvnSiteService : Error retrieving release version : " + e.getMessage( ), e );
        }

        String strLastRelease = ( !list.isEmpty( ) ) ? list.get( list.size( ) - 1 ) : null;

        if ( strLastRelease != null && strLastRelease.contains( "-" ) )
        {

            String [ ] tabRelease = strLastRelease.split( "-" );
            strLastRelease = tabRelease [tabRelease.length - 1];
        }
        else
        {
            strLastRelease = "";
        }
        return strLastRelease;

    }

    /**
     * Gets anchor list using more optimized method.
     *
     * @param strHtml
     *            The HTML code
     * @param strPrefix
     *            the str prefix
     * @return The list
     */
    private static List<String> getAnchorsList( String strHtml, String strPrefix )
    {
        List<String> list = new ArrayList<String>( );
        String strCurrent = strHtml;

        int nPos = strCurrent.indexOf( "<a " );

        while ( nPos > 0 )
        {
            strCurrent = strCurrent.substring( nPos );

            int nEndTag = strCurrent.indexOf( '>' );
            int nTagEnd = strCurrent.indexOf( "</a>" );
            String strTag = strCurrent.substring( nEndTag + 1, nTagEnd ).replaceAll( "\\/", "" );
            if ( strTag.startsWith( strPrefix ) )
            {
                list.add( strTag );
            }
            strCurrent = strCurrent.substring( nTagEnd + 4 );
            nPos = strCurrent.indexOf( "<a " );
        }

        return list;
    }

    /**
     * Build an authenticathor to access the site repository.
     *
     * @param strLogin
     *            the str login
     * @param strPassword
     *            the str password
     * @return The authenticator
     */
    private static RequestAuthenticator getSiteAuthenticator( String strLogin, String strPassword )
    {

        return new BasicAuthorizationAuthenticator( strLogin, strPassword );
    }

    /**
     * Do checkout repository.
     *
     * @param context
     *            the context
     * @param strLogin
     *            the str login
     * @param strPassword
     *            the str password
     * @return the string
     */
    @Override
    public String doCheckoutRepository( WorkflowReleaseContext context, String strLogin, String strPassword )
    {
        CommandResult commandResult = context.getCommandResult( );

        ReleaserUtils.logStartAction( context, " Checkout Svn" );

        String strLocalBasePath = ReleaserUtils.getLocalPath( context );

        File file = new File( strLocalBasePath );

        if ( file.exists( ) )
        {

            commandResult.getLog( ).append( "Local SVN  " + strLocalBasePath + " exist\nCleaning Local folder...\n" );
            if ( !FileUtils.delete( file, commandResult.getLog( ) ) )
            {
                commandResult.setError( commandResult.getLog( ).toString( ) );

            }
            commandResult.getLog( ).append( "Local SVN  has been cleaned\n" );
        }

        // PROGRESS 5%
        commandResult.setProgressValue( commandResult.getProgressValue( ) + 5 );

        ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( strLogin, strPassword );

        ReleaseSvnCheckoutClient updateClient = new ReleaseSvnCheckoutClient( authManager, SVNWCUtil.createDefaultOptions( false ) );

        commandResult.getLog( ).append( "Checkout SVN  ...\n" );
        Long nLastCommitId = null;

        try
        {
            nLastCommitId = SvnUtils.doSvnCheckout( SvnUtils.getRepoUrl( context.getReleaserResource( ).getScmUrl( ) ), strLocalBasePath, updateClient,
                    commandResult );

            SvnUtils.getLastRevision( strLocalBasePath, strLogin, strPassword );
        }
        catch( Exception e )
        {
            ReleaserUtils.addTechnicalError( commandResult, "errreur lors du checkout du composant" + e.getMessage( ), e );
        }

        if ( nLastCommitId != null )
        {
            context.setRefBranchReleaseFrom( nLastCommitId.toString( ) );
        }
        // PROGRESS 10%
        commandResult.setProgressValue( commandResult.getProgressValue( ) + 5 );

        if ( context.getSite( ) == null
                && ComponentService.getService( ).isErrorSnapshotComponentInformations( context.getComponent( ), ReleaserUtils.getLocalPomPath( context ) ) )
        {
            ReleaserUtils.addTechnicalError( commandResult, "The checkout component does not match the release informations" );
        }

        ReleaserUtils.logEndAction( context, "Checkout Svn Component " );
        return ConstanteUtils.CONSTANTE_EMPTY_STRING;
    }

    /**
     * Update develop branch.
     *
     * @param context
     *            the context
     * @param locale
     *            the locale
     * @param strMessage
     *            the str message
     */
    @Override
    public void updateDevelopBranch( WorkflowReleaseContext context, Locale locale, String strMessage )
    {

        String strLogin = context.getReleaserUser( ).getCredential( context.getReleaserResource( ).getRepoType( ) ).getLogin( );
        String strPassword = context.getReleaserUser( ).getCredential( context.getReleaserResource( ).getRepoType( ) ).getPassword( );
        String strLocalPath = ReleaserUtils.getLocalPath( context );

        ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( strLogin, strPassword );

        ReleaseSvnCommitClient commitClient = new ReleaseSvnCommitClient( authManager, SVNWCUtil.createDefaultOptions( false ) );

        try
        {
            SvnUtils.doCommit( strLocalPath, strMessage, commitClient );
        }
        catch( Exception e )
        {

            AppLogService.error( e );
            ReleaserUtils.addTechnicalError( context.getCommandResult( ), e.getMessage( ), e );
        }

    }

    /**
     * Update master branch.
     *
     * @param context
     *            the context
     * @param locale
     *            the locale
     */
    @Override
    public void updateMasterBranch( WorkflowReleaseContext context, Locale locale )
    {
        // TODO Auto-generated method stub

    }

    /**
     * Rollback release.
     *
     * @param context
     *            the context
     * @param locale
     *            the locale
     */
    @Override
    public void rollbackRelease( WorkflowReleaseContext context, Locale locale )
    {

        String strLogin = context.getReleaserUser( ).getCredential( context.getReleaserResource( ).getRepoType( ) ).getLogin( );
        String strPassword = context.getReleaserUser( ).getCredential( context.getReleaserResource( ).getRepoType( ) ).getPassword( );
        String strLocalPath = ReleaserUtils.getLocalPath( context );

        ReleaserUtils.logStartAction( context, " Rollback Release prepare" );

        SvnUtils.update( strLocalPath, strLogin, strPassword );
        Long lastRevision = SvnUtils.getLastRevision( strLocalPath, strLogin, strPassword );

        Long lastCommitBeforeRelease = context.getRefBranchReleaseFrom( ) != null ? new Long( context.getRefBranchReleaseFrom( ) ) : null;

        if ( lastRevision != null && lastCommitBeforeRelease != null && lastRevision != lastCommitBeforeRelease )
        {

            SvnUtils.revert( strLocalPath, SvnUtils.getRepoUrl( context.getReleaserResource( ).getScmUrl( ) ), strLogin, strPassword, lastRevision,
                    lastCommitBeforeRelease );

            ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( strLogin, strPassword );

            ReleaseSvnCommitClient commitClient = new ReleaseSvnCommitClient( authManager, SVNWCUtil.createDefaultOptions( false ) );

            try
            {
                SvnUtils.doCommit( strLocalPath, "[site-release]-Revert after error during release", commitClient );
            }
            catch( Exception e )
            {

                AppLogService.error( e );
                ReleaserUtils.addTechnicalError( context.getCommandResult( ), e.getMessage( ), e );
            }

        }
        ReleaserUtils.logEndAction( context, " Rollback Release prepare" );

    }

    /**
     * Checkout develop branch.
     *
     * @param context
     *            the context
     * @param locale
     *            the locale
     */
    @Override
    public void checkoutDevelopBranch( WorkflowReleaseContext context, Locale locale )
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void updateBranch( WorkflowReleaseContext context, String strBranch, Locale locale, String strMessage )
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void checkoutBranch( WorkflowReleaseContext context, String strBranch, Locale locale )
    {
        // TODO Auto-generated method stub

    }

}