blob: 6dafcf55fe27550c04a2e7638850c048ed2d3e5b [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2019-2021 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.converters.headless.app;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.felix.service.command.Descriptor;
import org.apache.felix.service.command.Parameter;
import org.eclipse.app4mc.amalthea.converters.common.MigrationException;
import org.eclipse.app4mc.amalthea.converters.common.MigrationHelper;
import org.eclipse.app4mc.amalthea.converters.common.MigrationInputFile;
import org.eclipse.app4mc.amalthea.converters.common.MigrationProcessor;
import org.eclipse.app4mc.amalthea.converters.common.MigrationSettings;
import org.eclipse.app4mc.amalthea.converters.common.MigrationStatusCode;
import org.eclipse.app4mc.amalthea.converters.common.utils.ModelVersion;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Command to execute a AMALTHEA model migration. Is registered as Felix Gogo Shell command.
*/
// suppress logger vulnerability because we explicitly want to write to the console for the executable jar use case
@SuppressWarnings(value = { "java:S1148", "java:S106" })
@Component(
property = {
"osgi.command.scope:String=app4mc",
"osgi.command.function:String=convert"
},
service = ModelMigrationCommand.class)
public class ModelMigrationCommand {
@Reference
MigrationProcessor migrationProcessor;
@Descriptor("Start an APP4MC AMALTHEA model migration")
public void convert(
@Descriptor("The model version to which the model should be migrated to")
@Parameter(absentValue = "latest", names = { "-v", "--version" })
String modelVersion,
@Descriptor("true/false whether the migration should be performed recursive on the provided folder or not (default=false)")
@Parameter(absentValue = "false", names = { "-r", "--recursive" } )
boolean recursive,
@Descriptor("true/false whether the backup files per model file should be created or not (default=false)")
@Parameter(absentValue = "false", names = { "-nb", "--nobackup" } )
boolean noBackup,
@Descriptor("The filename of the model file or the folder that contains model files to migrate")
String filename) {
Path modelFilePath = Paths.get(filename).toAbsolutePath();
if (!Files.exists(modelFilePath)) {
System.err.println("Model file or folder \""+ filename + "\" does not exist!");
return;
}
// build up MigrationSettings
// same as AmaltheaModelMigrationHandler#collectInput
String outputModelVersion = ModelVersion.getLatestVersion();
if (!"latest".equals(modelVersion)) {
// verify the input
ModelVersion version = ModelVersion.getModelVersion(modelVersion);
if (version != null) {
outputModelVersion = modelVersion;
} else {
System.err.println("Migration model version " + modelVersion + " is invalid");
return;
}
}
if (Files.isDirectory(modelFilePath)) {
convertDirectory(modelFilePath, outputModelVersion, recursive, noBackup);
} else if (modelFilePath.toString().toLowerCase().endsWith(".amxmi")) {
// single file migration
// build up MigrationSettings
// same as AmaltheaModelMigrationHandler#collectInput
try (MigrationSettings migrationSettings = new MigrationSettings()) {
migrationSettings.setProject(modelFilePath.getParent().toFile());
migrationSettings.setMigrationModelVersion(outputModelVersion);
convert(Arrays.asList(modelFilePath.toFile()), migrationSettings, noBackup);
}
} else {
System.err.println("Given parameter \"" + filename + "\" is neither a directory nor a model file!");
}
}
private void convertDirectory(Path modelFilePath, String outputModelVersion, boolean recursive, boolean noBackup) {
try (Stream<Path> directoryStream = Files.walk(modelFilePath, 1)) {
List<File> modelFiles = directoryStream
.filter(Files::isRegularFile)
.filter(file -> file.toString().toLowerCase().endsWith(".amxmi"))
.map(Path::toFile)
.collect(Collectors.toList());
if (!modelFiles.isEmpty()) {
try (MigrationSettings migrationSettings = new MigrationSettings()) {
migrationSettings.setProject(modelFilePath.toFile());
migrationSettings.setMigrationModelVersion(outputModelVersion);
convert(modelFiles, migrationSettings, noBackup);
}
}
} catch (IOException e) {
System.err.println("Failed to load model files");
e.printStackTrace();
return;
}
if (recursive) {
// check for directories and process each directory as separate migration
try (Stream<Path> directoryStream = Files.walk(modelFilePath, 1)) {
List<Path> modelDirectories = directoryStream
.filter(Files::isDirectory)
.filter(file -> {
try {
return !Files.isSameFile(file, modelFilePath);
} catch (IOException e) {
return false;
}
})
.collect(Collectors.toList());
for (Path path : modelDirectories) {
convertDirectory(path, outputModelVersion, recursive, noBackup);
}
} catch (IOException e) {
System.err.println("Failed to load model files");
e.printStackTrace();
}
}
}
private void convert(List<File> inputFiles, MigrationSettings migrationSettings, boolean noBackup) {
// same as ModelLoaderJob
try {
List<MigrationInputFile> modelFiles = MigrationHelper.populateModels(inputFiles, migrationSettings);
migrationSettings.getMigModelFiles().addAll(modelFiles);
} catch (Exception e) {
System.err.println("Failed to load model files");
e.printStackTrace();
return;
}
// same as AmaltheaModelMigrationHandler#JobChangeListener
try {
boolean inputValid = MigrationHelper.isInputModelVersionValid(migrationSettings);
if (!inputValid) {
System.err.println("Model migration stopped in " + migrationSettings.getOutputDirectoryLocation() + " as selected model files belong to different versions");
} else {
if (migrationSettings.getInputModelVersion() != null
&& ModelVersion.getLatestVersion().equals(migrationSettings.getInputModelVersion())) {
System.err.println("Selected models are compatible to latest AMALTHEA meta-model version "
+ ModelVersion.getLatestVersion()
+ ".\nIt is not required to migrate the models in "
+ migrationSettings.getOutputDirectoryLocation());
} else {
// check if a migration needs to be executed
Map<String, String> migStepEntries = MigrationHelper.generateMigrationSteps(
migrationSettings.getInputModelVersion(),
migrationSettings.getMigrationModelVersion());
if (migStepEntries.size() == 0) {
System.err.println("Migration not supported for the selected model versions.\nInput Model version : \""
+ migrationSettings.getInputModelVersion()
+ "\" Output Model Version : \""
+ migrationSettings.getMigrationModelVersion()
+ "\"");
return;
}
// set the file parent folder as output location to convert the file at source
MigrationInputFile migrationInputFile = migrationSettings.getMigModelFiles().get(0);
migrationSettings.setOutputDirectoryLocation(migrationInputFile.getOriginalFile().getParent());
// Rename or copy the original files to filename_currentversion.amxmi
boolean backupSucceeded = true;
if (!noBackup) {
for (MigrationInputFile input : migrationSettings.getMigModelFiles()) {
backupSucceeded = MigrationHelper.createBackupFile(input);
}
}
if (backupSucceeded) {
//now call migration job to migrate the file to latest Amalthea version
// same as ModelMigrationJob
int result = migrationProcessor.execute(migrationSettings, null);
switch (result) {
case MigrationStatusCode.UNSUPPORTED_MODEL_VERSIONS:
System.err.println(MessageFormat.format("Migration in {0} not supported for the selected model versions. \nInput Model version : \"{1}\" Output Model Version : \"{2}\"",
migrationSettings.getOutputDirectoryLocation(),
migrationSettings.getInputModelVersion(),
migrationSettings.getMigrationModelVersion()));
break;
case MigrationStatusCode.ERROR:
System.err.println("Error during migration in " + migrationSettings.getOutputDirectoryLocation());
break;
default:
System.out.println("Model Migration in " + migrationSettings.getOutputDirectoryLocation() + " successful !!");
}
} else {
// do nothing as we could not backup source and this can data loss to user if he
// does not intend to loose his original model file
System.err.println("Migration Stopped : Source files could not be backed up before migration in "
+ migrationSettings.getOutputDirectoryLocation());
}
}
}
} catch (MigrationException e) {
System.err.println("Error during migration in " + migrationSettings.getOutputDirectoryLocation() + " : " + e.getLocalizedMessage());
}
}
}