001/*
002 *  Copyright 2020 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.migration.action.impl;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.MalformedURLException;
021import java.util.Map;
022
023import javax.script.ScriptException;
024
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.io.IOUtils;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceNotFoundException;
034import org.apache.excalibur.source.SourceResolver;
035import org.slf4j.LoggerFactory;
036
037import org.ametys.core.migration.MigrationException;
038import org.ametys.core.migration.action.Action;
039import org.ametys.core.migration.action.data.ActionData;
040import org.ametys.core.migration.action.data.impl.ScriptActionData;
041import org.ametys.core.migration.version.Version;
042import org.ametys.plugins.core.ui.script.ScriptHandler;
043import org.ametys.runtime.plugin.component.AbstractLogEnabled;
044
045/**
046 * SQL action : A script will be executed
047 */
048public class ScriptAction extends AbstractLogEnabled implements Action, Serviceable
049{
050    /** Source resolver */
051    protected SourceResolver _sourceResolver;
052    
053    /** Script Handler */
054    protected ScriptHandler _scriptHandler;
055
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
059        _scriptHandler = (ScriptHandler) manager.lookup(ScriptHandler.ROLE);
060    }
061    
062    public void doAction(ActionData actionData) throws MigrationException
063    {
064        getLogger().debug("Start initialization for : {}", actionData.toString());
065        
066        if (!(actionData instanceof ScriptActionData))
067        {
068            throw new MigrationException("Script Upgrade can only be created for a Script upgrade, this is not the case for upgrade : " + actionData.toString());
069        }
070        
071        ScriptActionData scriptActionData = (ScriptActionData) actionData;
072        try
073        {
074            String script = getScript(scriptActionData);
075            
076            Version version = actionData.getVersion();
077            Map<String, Object> variables = Map.of(
078                "version", version,
079                "actionData", actionData,
080                "logger", LoggerFactory.getLogger(getLogger().getName() + "." + version.getComponentId())
081            );
082            
083            // Execute the script in the admin workspace 
084            Map<String, Object> scriptResult = _scriptHandler.executeScript(script, variables, "admin");
085            if (!StringUtils.isAllBlank((String) scriptResult.get("error"), (String) scriptResult.get("message"), (String) scriptResult.get("stacktrace")))
086            {
087                throw new MigrationException("Error while executing script to upgrade component '" + actionData.getVersion().getComponentId() + "' to version '" + actionData.getVersionNumber() + "'. Error : '" + scriptResult.get("error") + "', message : '" + scriptResult.get("message") + "', stacktrace : '" + scriptResult.get("stacktrace") + "'");
088            }
089        }
090        catch (ScriptException e)
091        {
092            throw new MigrationException("Error while executing script to upgrade : '" + actionData.toString() + "'", e);
093        }
094        
095        getLogger().debug("End upgrade for : {}", actionData.toString());
096    }
097
098    /**
099     * Get the content of the script from the actionData
100     * @param scriptActionData actionData containing info about the upgrade
101     * @return The script as a String (either from the config or from a file to parse)
102     * @throws MigrationException Something went wrong
103     */
104    protected String getScript(ScriptActionData scriptActionData) throws MigrationException
105    {
106        String script;
107        if (StringUtils.isNotBlank(scriptActionData.getScript()))
108        {
109            script = scriptActionData.getScript();
110        }
111        else
112        {
113            String uri = "plugin:" + scriptActionData.getPlugin() + "://" + scriptActionData.getFile();
114            Source source = null;
115            try
116            {
117                source = _sourceResolver.resolveURI(uri);
118                
119                if (!source.exists())
120                {
121                    throw new SourceNotFoundException("URI '" + uri + "' does not exist.");
122                }
123                try (InputStream is = source.getInputStream())
124                {
125                    script = IOUtils.toString(is, "UTF-8");
126                }
127            }
128            catch (MalformedURLException e)
129            {
130                throw new MigrationException("Impossible to find the script for the upgrade : " + scriptActionData.toString(), e);
131            }
132            catch (IOException e)
133            {
134                throw new MigrationException("Impossible to run the upgrade : " + scriptActionData.toString(), e);
135            }
136            finally
137            {
138                if (source != null)
139                {
140                    _sourceResolver.release(source);
141                }
142            }
143        }
144        return "function main() { \n " + script + " \n }";
145    }
146    
147    public ActionData generateActionData(String id, Version version, String comment, String from, String type, String pluginName, Configuration configuration, boolean restartRequired) throws MigrationException, ConfigurationException
148    {
149        return new ScriptActionData(id, version, comment, from, type, pluginName, configuration, restartRequired);
150    }
151}