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                "logger", LoggerFactory.getLogger(getLogger().getName() + "." + version.getComponentId())
080            );
081            
082            // Execute the script in the admin workspace 
083            Map<String, Object> scriptResult = _scriptHandler.executeScript(script, variables, "admin");
084            if (!StringUtils.isAllBlank((String) scriptResult.get("error"), (String) scriptResult.get("message"), (String) scriptResult.get("stacktrace")))
085            {
086                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") + "'");
087            }
088        }
089        catch (ScriptException e)
090        {
091            throw new MigrationException("Error while executing script to upgrade : '" + actionData.toString() + "'", e);
092        }
093        
094        getLogger().debug("End upgrade for : {}", actionData.toString());
095    }
096
097    /**
098     * Get the content of the script from the actionData
099     * @param scriptActionData actionData containing info about the upgrade
100     * @return The script as a String (either from the config or from a file to parse)
101     * @throws MigrationException Something went wrong
102     */
103    protected String getScript(ScriptActionData scriptActionData) throws MigrationException
104    {
105        String script;
106        if (StringUtils.isNotBlank(scriptActionData.getScript()))
107        {
108            script = scriptActionData.getScript();
109        }
110        else
111        {
112            String uri = "plugin:" + scriptActionData.getPlugin() + "://" + scriptActionData.getFile();
113            Source source = null;
114            try
115            {
116                source = _sourceResolver.resolveURI(uri);
117                
118                if (!source.exists())
119                {
120                    throw new SourceNotFoundException("URI '" + uri + "' does not exist.");
121                }
122                try (InputStream is = source.getInputStream())
123                {
124                    script = IOUtils.toString(is, "UTF-8");
125                }
126            }
127            catch (MalformedURLException e)
128            {
129                throw new MigrationException("Impossible to find the script for the upgrade : " + scriptActionData.toString(), e);
130            }
131            catch (IOException e)
132            {
133                throw new MigrationException("Impossible to run the upgrade : " + scriptActionData.toString(), e);
134            }
135            finally
136            {
137                if (source != null)
138                {
139                    _sourceResolver.release(source);
140                }
141            }
142        }
143        return "function main() { \n " + script + " \n }";
144    }
145    
146    public ActionData generateActionData(String id, Version version, String comment, String from, String type, String pluginName, Configuration configuration) throws MigrationException, ConfigurationException
147    {
148        return new ScriptActionData(id, version, comment, from, type, pluginName, configuration);
149    }
150}