/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2019 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.web.scripts;

import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status;
import javax.transaction.UserTransaction;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TooBusyException;
import org.alfresco.repo.web.scripts.bean.LoginPost;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.extensions.webscripts.AbstractRuntimeContainer;
import org.springframework.extensions.webscripts.Authenticator;
import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
import org.springframework.extensions.webscripts.Description.RequiredTransaction;
import org.springframework.extensions.webscripts.Description.RequiredTransactionParameters;
import org.springframework.extensions.webscripts.Description.TransactionCapability;
import org.springframework.extensions.webscripts.ServerModel;
import org.springframework.extensions.webscripts.WebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;


/**
 * Repository (server-tier) container for Web Scripts
 * 
 * @author steveglover
 * @author davidc
 */
public class RepositoryContainer extends AbstractRuntimeContainer
{
    // Logger
    protected static final Log logger = LogFactory.getLog(RepositoryContainer.class);

    /** Component Dependencies */
    private Repository repository;
    private RepositoryImageResolver imageResolver;
    private TransactionService transactionService;
    private RetryingTransactionHelper fallbackTransactionHelper;
    private AuthorityService authorityService;
    private DescriptorService descriptorService;

    private boolean encryptTempFiles = false;
    private String tempDirectoryName = null;
    private int memoryThreshold = 4 * 1024 * 1024; // 4mb
    private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
    private TempOutputStreamFactory streamFactory = null;
    private TempOutputStreamFactory responseStreamFactory = null;
    private String preserveHeadersPattern = null;

    private Class<?>[] notPublicExceptions = new Class<?>[] {};
    private Class<?>[] publicExceptions = new Class<?>[] {};

    /*
     * Shame init is already used (by TenantRepositoryContainer).
     */
    public void setup()
    {
        File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
        this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false);
        this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, true);
    }

    public void setEncryptTempFiles(Boolean encryptTempFiles)
    {
		if(encryptTempFiles != null)
		{
			this.encryptTempFiles = encryptTempFiles.booleanValue();
		}
	}

	public void setTempDirectoryName(String tempDirectoryName)
	{
		this.tempDirectoryName = tempDirectoryName;
	}

	public void setMemoryThreshold(Integer memoryThreshold)
	{
		if(memoryThreshold != null)
		{
			this.memoryThreshold = memoryThreshold.intValue();
		}
	}

	public void setMaxContentSize(Long maxContentSize)
	{
		if(maxContentSize != null)
		{
			this.maxContentSize = maxContentSize.longValue();
		}
	}

    public void setPreserveHeadersPattern(String preserveHeadersPattern)
    {
        this.preserveHeadersPattern = preserveHeadersPattern;
    }

	/**
     * @param repository Repository
     */
    public void setRepository(Repository repository)
    {
        this.repository = repository;
    }

    /**
     * @param imageResolver RepositoryImageResolver
     */
    public void setRepositoryImageResolver(RepositoryImageResolver imageResolver)
    {
        this.imageResolver = imageResolver;
    }
    
    /**
     * @param transactionService TransactionService
     */
    public void setTransactionService(TransactionService transactionService)
    {
        this.transactionService = transactionService;
    }

    /**
     * @param fallbackTransactionHelper an unlimited transaction helper used to generate error responses
     */
    public void setFallbackTransactionHelper(RetryingTransactionHelper fallbackTransactionHelper)
    {
        this.fallbackTransactionHelper = fallbackTransactionHelper;
    }

    /**
     * @param descriptorService DescriptorService
     */
    public void setDescriptorService(DescriptorService descriptorService)
    {
        this.descriptorService = descriptorService;
    }

    /**
     * @param authorityService AuthorityService
     */
    public void setAuthorityService(AuthorityService authorityService)
    {
        this.authorityService = authorityService;
    }

    /**
     * Exceptions which may contain information that cannot be displayed in UI 
     * 
     * @param notPublicExceptions - {@link Class}&lt;?&gt;[] instance which contains list of not public exceptions
     */
    public void setNotPublicExceptions(List<Class<?>> notPublicExceptions)
    {
        this.notPublicExceptions = new Class<?>[] {};
        if((null != notPublicExceptions) && !notPublicExceptions.isEmpty())
        {
            this.notPublicExceptions = notPublicExceptions.toArray(this.notPublicExceptions);
        }
    }

    public Class<?>[] getNotPublicExceptions()
    {
        return notPublicExceptions;
    }

    /**
     * Exceptions which may contain information that need to display in UI
     *
     * @param publicExceptions - {@link Class}&lt;?&gt;[] instance which contains list of public exceptions
     */
    public void setPublicExceptions(List<Class<?>> publicExceptions)
    {
        this.publicExceptions = new Class<?>[] {};
        if((null != publicExceptions) && !publicExceptions.isEmpty())
        {
            this.publicExceptions = publicExceptions.toArray(this.publicExceptions);
        }
    }

    public Class<?>[] getPublicExceptions()
    {
        return publicExceptions;
    }

    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.Container#getDescription()
     */
    public ServerModel getDescription()
    {
        return new RepositoryServerModel(descriptorService.getCurrentRepositoryDescriptor(), descriptorService.getServerDescriptor());
    }

    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.AbstractRuntimeContainer#getScriptParameters()
     */
    public Map<String, Object> getScriptParameters()
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.putAll(super.getScriptParameters());
        addRepoParameters(params);
        return params;
    }

    /*
     * (non-Javadoc)
     * @see org.alfresco.web.scripts.AbstractRuntimeContainer#getTemplateParameters()
     */
    public Map<String, Object> getTemplateParameters()
    {
        // Ensure we have a transaction - we might be generating the status template after the main transaction failed
        return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback<Map<String, Object>>()
        {
            public Map<String, Object> execute() throws Throwable
            {
                Map<String, Object> params = new HashMap<String, Object>();
                params.putAll(RepositoryContainer.super.getTemplateParameters());
                params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
                addRepoParameters(params);
                return params;
            }
        }, true);
    }

    /**
     * Add Repository specific parameters
     * 
     * @param params Map<String, Object>
     */
    private void addRepoParameters(Map<String, Object> params)
    {
        if (AlfrescoTransactionSupport.getTransactionId() != null &&
            AuthenticationUtil.getFullAuthentication() != null)
        {
            NodeRef rootHome = repository.getRootHome();
            if (rootHome != null)
            {
                params.put("roothome", rootHome);
            }
            NodeRef companyHome = repository.getCompanyHome();
            if (companyHome != null)
            {
                params.put("companyhome", companyHome);
            }
            NodeRef person = repository.getFullyAuthenticatedPerson();
            if (person != null)
            {
                params.put("person", person);
                NodeRef userHome = repository.getUserHome(person);
                if (userHome != null)
                {
                    params.put("userhome", userHome);
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.RuntimeContainer#executeScript(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse, org.alfresco.web.scripts.Authenticator)
     */
    public void executeScript(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth)
        throws IOException
    {
        try
        {
            executeScriptInternal(scriptReq, scriptRes, auth);
        }
        catch (RuntimeException e)
        {
            Throwable hideCause = ExceptionStackUtil.getCause(e, notPublicExceptions);
            Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions);
            if (displayCause == null && hideCause != null)
            {
                AlfrescoRuntimeException alf = null;
                if (e instanceof AlfrescoRuntimeException)
                {
                    alf = (AlfrescoRuntimeException) e;
                }
                else
                {
                    // The message will not have a numerical identifier
                    alf = new AlfrescoRuntimeException("WebScript execution failed", e);
                }
                String num = alf.getNumericalId();
                logger.error("Server error (" + num + ")", e);
                throw new RuntimeException("Server error (" + num + ").  Details can be found in the server logs.");
            }
            else
            {
                throw e;
            }
        }
    }
    
    protected void executeScriptInternal(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth)
        throws IOException
    {
        final WebScript script = scriptReq.getServiceMatch().getWebScript();
        final Description desc = script.getDescription();
        final boolean debug = logger.isDebugEnabled();
		        
        // Escalate the webscript declared level of authentication to the container required authentication
        // eg. must be guest if MT is enabled unless credentials are empty
        RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication();
        final RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication());
        final boolean isGuest = scriptReq.isGuest();
        
        if (required == RequiredAuthentication.none)
        {
            // TODO revisit - cleared here, in-lieu of WebClient clear
            //AuthenticationUtil.clearCurrentSecurityContext();
            
            transactionedExecuteAs(script, scriptReq, scriptRes);
        }
        else if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest)
        {
            throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access.");
        }
        else
        {
            try
            {
                AuthenticationUtil.pushAuthentication();
                
                //
                // Determine if user already authenticated
                //
                if (debug)
                {
                    String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
                    logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
                    logger.debug("Authentication required: " + required);
                    logger.debug("Guest login requested: " + isGuest);
                }
                
                //
                // Apply appropriate authentication to Web Script invocation
                //
                RetryingTransactionCallback<Boolean> authWork = new RetryingTransactionCallback<Boolean>()
                {
                    public Boolean execute() throws Exception
                    {
                        if (auth == null || auth.authenticate(required, isGuest))
                        {
                            // The user will now have been authenticated, based on HTTP Auth, Ticket etc
                            // Check that the user they authenticated as has appropriate access to the script
                            
                            // Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more
                            if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin)
                            {
                                String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser();
                                String runAsUser = AuthenticationUtil.getRunAsUser();
                                
                                if ( (authenticatedUser == null) ||
                                     (authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) ||
                                     (!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) )
                                {
                                    throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access.");
                                }
                            }
                            
                            // Check to see if they're admin or system on an Admin only script
                            if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName())))
                            {
                                throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access.");
                            }
                            
                            if (debug)
                            {
                                String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
                                logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
                            }
                            
                            return true;
                        }
                        return false;
                    }        
                };
                
                boolean readOnly = transactionService.isReadOnly();
                boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
                if (transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew))
                {
                    // Execute Web Script if authentication passed
                    // The Web Script has its own txn management with potential runAs() user
                    transactionedExecuteAs(script, scriptReq, scriptRes);
                }
                else
                {
                    throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId());
                }
            }
            finally
            {
                //
                // Reset authentication for current thread
                //
                AuthenticationUtil.popAuthentication();
                
                if (debug)
                {
                    String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
                    logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
                }
            }
        }
    }

    /**
     * Execute script within required level of transaction
     * 
     * @param script WebScript
     * @param scriptReq WebScriptRequest
     * @param scriptRes WebScriptResponse
     * @throws IOException
     */
    protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes)
        throws IOException
    {
        try
        {
            final Description description = script.getDescription();
            if (description.getRequiredTransaction() == RequiredTransaction.none)
            {
                script.execute(scriptReq, scriptRes);
            }
            else
            {
                final BufferedRequest bufferedReq;
                final BufferedResponse bufferedRes;
                RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters();
                if (trxParams.getCapability() == TransactionCapability.readwrite)
                {
                    if (trxParams.getBufferSize() > 0)
                    {
                        if (logger.isDebugEnabled())
                            logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize());

                        // create buffered request and response that allow transaction retrying
                        bufferedReq = new BufferedRequest(scriptReq, streamFactory);
                        bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory);
                    }
                    else
                    {
                        if (logger.isDebugEnabled())
                            logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0");
                        bufferedReq = null;
                        bufferedRes = null;
                    }
                }
                else
                {
                    bufferedReq = null;
                    bufferedRes = null;
                }
            
                // encapsulate script within transaction
                RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
                {
                    public Object execute() throws Exception
                    {
                        try
                        {
                            if (logger.isDebugEnabled())
                                logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," 
                                        + description.getRequiredTransactionParameters().getCapability());

                            if (bufferedRes == null)
                            {
                                script.execute(scriptReq, scriptRes);                            
                            }
                            else
                            {
                                // Reset the request and response in case of a transaction retry
                                bufferedReq.reset();
                                // REPO-4388 don't reset specified headers
                                bufferedRes.reset(preserveHeadersPattern);
                                script.execute(bufferedReq, bufferedRes);
                            }
                        }
                        catch(Exception e)
                        {
                            if (logger.isDebugEnabled())
                            {
                                logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage());
                                // Note: user transaction shouldn't be null, but just in case inside this exception handler
                                UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
                                if (userTrx != null)
                                {
                                    logger.debug("Transaction status: " + userTrx.getStatus());
                                }
                            }
                           
                            UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
                            if (userTrx != null)
                            {
                                if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK)
                                {
                                    if (logger.isDebugEnabled())
                                        logger.debug("Marking web script transaction for rollback");
                                    try
                                    {
                                        userTrx.setRollbackOnly();
                                    }
                                    catch(Throwable re)
                                    {
                                        if (logger.isDebugEnabled())
                                            logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
                                    }
                                }
                            }
                        
                            // re-throw original exception for retry
                            throw e;
                        }
                        finally
                        {
                           if (logger.isDebugEnabled())
                                logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," 
                                        + description.getRequiredTransactionParameters().getCapability());
                        }
                    
                        return null;
                    }        
                };
        
                boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly;
                boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew;
            
                // log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should
                // NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179.
                if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase(description.getMethod()))
                {
                    logger.debug("Webscript with URL '" + scriptReq.getURL() + 
                               "' is a GET request but it's descriptor has declared a readwrite transaction is required");
                }
            
                try
                {
                    RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
                    if(script instanceof LoginPost)
                    {
                        //login script requires read-write transaction because of authorization intercepter
                        transactionHelper.setForceWritable(true);
                    }
                    transactionHelper.doInTransaction(work, readonly, requiresNew);
                }
                catch (TooBusyException e)
                {
                    // Map TooBusyException to a 503 status code
                    throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e);
                }
                finally
                {
                    // Get rid of any temporary files
                    if (bufferedReq != null)
                    {
                        bufferedReq.close();
                    }
                }

                // Ensure a response is always flushed after successful execution
                if (bufferedRes != null)
                {
                    bufferedRes.writeResponse();
                }
            
            }
        }
        catch (IOException ioe)
        {
            Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class);
            Class<?> clientAbortException = null;
            try
            {
                clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException");
            }
            catch (ClassNotFoundException e)
            {
                // do nothing
            }
            // Note: if you need to look for more exceptions in the stack, then create a static array and pass it in
            if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null))
            {
                if (logger.isDebugEnabled())
                {
                    logger.warn("Client has cut off communication", ioe);
                }
                else
                {
                    logger.info("Client has cut off communication");
                }
            }
            else
            {
                throw ioe;
            }
        }
    }
    
    /**
     * Execute script within required level of transaction as required effective user.
     * 
     * @param script WebScript
     * @param scriptReq WebScriptRequest
     * @param scriptRes WebScriptResponse
     * @throws IOException
     */
    private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq,
            final WebScriptResponse scriptRes) throws IOException
    {
        String runAs = script.getDescription().getRunAs();
        if (runAs == null)
        {
            transactionedExecute(script, scriptReq, scriptRes);
        }
        else
        {
            RunAsWork<Object> work = new RunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    transactionedExecute(script, scriptReq, scriptRes);
                    return null;
                }
            };
            AuthenticationUtil.runAs(work, runAs);
        }
    }
    
    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.AbstractRuntimeContainer#onApplicationEvent(org.springframework.context.ApplicationEvent)
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event)
    {
        if (event instanceof ContextRefreshedEvent)
        {
            ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event;
            ApplicationContext refreshContext = refreshEvent.getApplicationContext();
            if (refreshContext != null && refreshContext.equals(applicationContext))
            {
                RunAsWork<Object> work = new RunAsWork<Object>()
                {
                    public Object doWork() throws Exception
                    {
                        reset();
                        return null;
                    }
                };
                AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName());
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.AbstractRuntimeContainer#getRequiredAuthentication()
     */
    @Override
    public RequiredAuthentication getRequiredAuthentication()
    {
        if (AuthenticationUtil.isMtEnabled())
        {
            return RequiredAuthentication.guest; // user or guest (ie. at least guest)
        }
        
        return RequiredAuthentication.none;
    }
    
    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.RuntimeContainer#authenticate(org.alfresco.web.scripts.Authenticator, org.alfresco.web.scripts.Description.RequiredAuthentication)
     */
    @Override
    public boolean authenticate(Authenticator auth, RequiredAuthentication required)
    {
        if (auth != null)
        {
            AuthenticationUtil.clearCurrentSecurityContext();
            
            return auth.authenticate(required, false);
        }
        
        return false;
    }

    /* (non-Javadoc)
     * @see org.alfresco.web.scripts.AbstractRuntimeContainer#reset()
     */
    @Override
    public void reset() 
    {
        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
        {
            public Object execute() throws Exception
            {
                internalReset();
                return null;
            }
        }, true, false);
    }
    
    private void internalReset()
    {
        super.reset();
    }
}