001/*
002 *  Copyright 2023 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.runtime.plugins.admin.migration;
017
018import java.time.Instant;
019import java.util.Map.Entry;
020
021import org.apache.avalon.framework.service.ServiceException;
022import org.apache.avalon.framework.service.ServiceManager;
023
024import org.ametys.core.migration.MigrationEngine;
025import org.ametys.core.migration.MigrationEngine.ActionData;
026import org.ametys.core.migration.MigrationEngine.MigrationComponent;
027import org.ametys.core.migration.MigrationEngine.VersionList;
028import org.ametys.core.migration.MigrationEngine.Versions;
029import org.ametys.core.migration.MigrationException;
030import org.ametys.core.migration.MigrationExtensionPoint;
031import org.ametys.core.migration.action.ActionConfiguration;
032import org.ametys.core.migration.action.ActionExtensionPoint;
033import org.ametys.core.migration.version.Version;
034import org.ametys.core.ui.Callable;
035import org.ametys.core.ui.StaticClientSideElement;
036
037/**
038 * Client side element with callables for migrations actions
039 */
040public class MigrationsActionsClientSideElement extends StaticClientSideElement
041{
042    private MigrationEngine _migrationEngine;
043    private MigrationExtensionPoint _migrationExtensionPoint;
044    private MigrationExtensionPoint _migrationDataExtensionPoint;
045    private ActionExtensionPoint _upgradeEP;
046
047    @Override
048    public void service(ServiceManager manager) throws ServiceException
049    {
050        super.service(manager);
051        
052        _migrationEngine = (MigrationEngine) manager.lookup(MigrationEngine.ROLE);
053        _migrationExtensionPoint = (MigrationExtensionPoint) manager.lookup(MigrationExtensionPoint.ROLE);
054        _migrationDataExtensionPoint = (MigrationExtensionPoint) manager.lookup(MigrationExtensionPoint.ROLE + "/internal");
055        _upgradeEP = (ActionExtensionPoint) manager.lookup(ActionExtensionPoint.ROLE_UPGRADE);
056    }
057    
058    /**
059     * Marks a version as done
060     * @param componentId the main component id
061     * @param internal true if the component is from internel migration
062     * @param versionListId the id of the {@link VersionList}
063     * @param versionNumber the number of the version to be deleted
064     * @param comment the comment associated to this operation
065     * @throws MigrationException if an error occurs while storing version
066     */
067    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
068    public void acknowledge(String componentId, boolean internal, String versionListId, String versionNumber, String comment) throws MigrationException
069    {
070        MigrationComponent component = internal ? _migrationDataExtensionPoint.getExtension(componentId) : _migrationExtensionPoint.getExtension(componentId);
071        
072        VersionList versionList = _getVersionList(component, versionListId);
073        
074        Version version = new Version(component, versionList.componentId(), component.versionStorage(), versionList.storageConfiguration(), versionNumber, Instant.now(), comment);
075        component.versionStorage().storeVersion(version);
076    }
077    
078    /**
079     * Deletes a version from storage
080     * @param componentId the main component id
081     * @param internal true if the component is from internel migration
082     * @param versionListId the id of the {@link VersionList}
083     * @param versionNumber the number of the version to be deleted
084     * @throws MigrationException if an error occurs while deleting version
085     */
086    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
087    public void deleteVersion(String componentId, boolean internal, String versionListId, String versionNumber) throws MigrationException
088    {
089        MigrationComponent component = internal ? _migrationDataExtensionPoint.getExtension(componentId) : _migrationExtensionPoint.getExtension(componentId);
090        
091        VersionList versionList = _getVersionList(component, versionListId);
092        
093        component.versionStorage().removeVersion(versionList.componentId(), versionNumber, versionList.storageConfiguration());
094    }
095    
096    /**
097     * Deletes all versions of a component from storage
098     * @param componentId the main component id
099     * @param internal true if the component is from internel migration
100     * @param versionListId the id of the {@link VersionList}
101     * @throws MigrationException if an error occurs while deleting versions
102     */
103    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
104    public void deleteAllVersions(String componentId, boolean internal, String versionListId) throws MigrationException
105    {
106        MigrationComponent component = internal ? _migrationDataExtensionPoint.getExtension(componentId) : _migrationExtensionPoint.getExtension(componentId);
107        
108        VersionList versionList = _getVersionList(component, versionListId);
109        
110        component.versionStorage().removeAllVersions(versionList.componentId(), versionList.storageConfiguration());
111    }
112    
113    /**
114     * (Re-)plays a migration action, optionnaly storing a new version
115     * @param componentId the main component id
116     * @param internal true if the component is from internel migration
117     * @param versionListId the id of the {@link VersionList}
118     * @param upgradeNumber the number of the action to be executed
119     * @param store if the version should be stored after execution
120     * @param comment the comment associated to this operation
121     * @return true if the action needs a restart after its execution
122     * @throws MigrationException  if an error occurs while executing action
123     */
124    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
125    public boolean doAction(String componentId, boolean internal, String versionListId, String upgradeNumber, boolean store, String comment) throws MigrationException
126    {
127        MigrationComponent component = internal ? _migrationDataExtensionPoint.getExtension(componentId) : _migrationExtensionPoint.getExtension(componentId);
128        
129        ActionConfiguration actionConfiguration = component.upgrades().stream()
130                                                                      .filter(ac -> ac.getVersionNumber().equals(upgradeNumber))
131                                                                      .findFirst()
132                                                                      .orElseThrow(() -> new MigrationException("no such upgrade: " + upgradeNumber));
133        
134        VersionList versionList = _getVersionList(component, versionListId);
135        
136        Version version = new Version(component, versionList.componentId(), component.versionStorage(), versionList.storageConfiguration(), null, null, null);
137        
138        if (versionList.additionalValues() != null)
139        {
140            for (Entry<String, Object> entry : versionList.additionalValues().entrySet())
141            {
142                version.addAdditionalValue(entry.getKey(), entry.getValue());
143            }
144        }
145        
146        boolean requiresRestart = _migrationEngine.doAction(new ActionData(version, null, actionConfiguration, versionList.id()), _upgradeEP);
147        
148        if (store)
149        {
150            // first remove the already stored version
151            component.versionStorage().removeVersion(versionList.componentId(), upgradeNumber, versionList.storageConfiguration());
152            
153            version.setComment(comment);
154            version.setExecutionInstant(Instant.now());
155            version.setVersionNumber(upgradeNumber);
156            
157            component.versionStorage().storeVersion(version);
158        }
159        
160        return requiresRestart;
161    }
162    
163    private VersionList _getVersionList(MigrationComponent component, String versionListId) throws MigrationException
164    {
165        Versions versions = _migrationEngine.getVersions(component);
166        VersionList versionList = _migrationEngine.getVersionList(versions, versionListId);
167        
168        if (versionList == null)
169        {
170            throw new MigrationException("There's no version list for id: " + versionListId + " in component: " + component);
171        }
172        
173        return versionList;
174    }
175}