SuperX-Kernmodul
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

533 lines
23 KiB

package de.superx.bin;
import static de.superx.servlet.SxSQL_Server.DEFAULT_MANDANTEN_ID;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import de.superx.job.ContainerNode;
import de.superx.rest.EtlJobApi;
import de.superx.rest.model.job.Component;
import de.superx.rest.model.job.JobExecutionStatus;
import de.superx.servlet.SuperXManager;
import de.superx.servlet.SxPools;
import de.superx.spring.HisInOneConfiguration;
import de.superx.spring.batch.His1DataSources;
import de.superx.spring.batch.tasklet.ExecuteSqlScriptTasklet;
import de.superx.spring.cli.config.CLIConfig;
import de.superx.spring.config.BatchConfig;
import de.superx.spring.config.DataJdbcConfiguration;
import de.superx.spring.config.ServiceConfig;
import de.superx.spring.service.BatchJobDescriptionAdapter;
import de.superx.spring.service.EntityJobDescriptionSource;
import de.superx.util.PathAndFileUtils;
/***
* This class provides functionality to run the component actions of the
* 'modernized component administration'
* of the HISinOne-BI via command line. That includes
* - listing ETL jobs
* - installing components
* - updating components
* - deinstalling components
* - running "Hauptladeroutine" ETL jobs
* - running "Unterladeroutine" ETL jobs
* The class is just meant to be a frontend, so it uses the same implementation as the web application
* (which happens to be the "EtlJobApi" bean)
* However, there are to be some things to be considered:
* - The "jobLauncher" bean from the "BatchConfig" class works asynchronously, which is fine for the
* web application context, but in the CLI context, it is overridden by a synchronous implementation
* (in fact the tweaks to get the configurations ready for the CLI context, are placed in the
* "CLIConfig" class).
* - In the web application context, the HISinOne-BI code in the HISinOne web application writes the
* database configuration to a file, which is then consumed by the "superx" application (the actual
* HISinOne-BI application). The command line application needs this file too, so before using it, you
* should start the web application one time, in order to have the file in place.
* @author witt
*
*/
public class ComponentAdminCLI {
private static GenericApplicationContext APPLICATION_CONTEXT = null;
private static String HELP_STRING = "Use this tool to run component actions via command line. "
+ "It needs the config file 'his1_databases.properties' inside the classpath; "
+ "this file gets written automatically when starting the web application.";
private static boolean FILESYSTEM = false;
private enum ExitStatus { SUCCESS, FAILURE };
static Logger logger = Logger.getLogger(ComponentAdminCLI.class);
public static void main(String[] args) {
ExitStatus exitStatus = ExitStatus.SUCCESS;
BasicConfigurator.configure(); // initializes console logging to stdout
Logger.getRootLogger().setLevel(Level.INFO);
System.setProperty(SuperXManager.SUPER_X_HISINONE_VERSION, "non-empty-value");
Options options = createOptions();
CommandLine parsedArgs = parseArgs(args, options);
logOptions(parsedArgs);
if (parsedArgs.hasOption("h")) {
printHelp(options);
} else {
// Optionen
if(parsedArgs.hasOption("f")) {
FILESYSTEM = true;
}
initSuperXManager();
if(parsedArgs.hasOption("s")) {
initTablesForEmptyDB();
}
if(parsedArgs.hasOption("m")) {
ExecuteSqlScriptTasklet.FAIL_ON_MISSING_SCRIPT = true;
}
if(parsedArgs.hasOption("lg")) {
setBatchLoggerToOneFile();
}
// Ausführung
SuperXManager.initKettleEnv(createContext());
if (parsedArgs.hasOption("la")) {
printAllJobs();
} else if (parsedArgs.hasOption("li")) {
printInstallableJobs();
} else if (parsedArgs.hasOption("le")) {
printEtlJobs();
} else if (parsedArgs.hasOption("i")) {
exitStatus = installComponents(parsedArgs);
} else if (parsedArgs.hasOption("d")) {
exitStatus = deinstallComponent(parsedArgs);
} else if (parsedArgs.hasOption("u")) {
exitStatus = upgradeComponents(parsedArgs);
} else if (parsedArgs.hasOption("ua")) {
exitStatus = upgradeAll();
} else if (parsedArgs.hasOption("e")) {
exitStatus = etlJobs(parsedArgs);
} else if (parsedArgs.hasOption("r")) {
exitStatus = reloadModule();
} else if (parsedArgs.hasOption("if")) {
exitStatus = installFunctions(parsedArgs);
} else {
printHelp(options);
}
}
System.exit(exitStatus.ordinal());
}
private static void logOptions(CommandLine parsedArgs) {
logger.info("Starting with the following options:");
for (Option opt : parsedArgs.getOptions()) {
logger.info(opt);
if(opt.getValues() != null && opt.getValues().length > 0) {
logger.info("Values for option " + opt.getOpt() + ": " + Arrays.asList(opt.getValues()));
}
}
}
private static void initTablesForEmptyDB() {
try {
GenericApplicationContext context = createContext();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
ContainerNode root = EntityJobDescriptionSource.getPreKernInstallJob();
componentApi.executeJob("eduetl", root);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void setBatchLoggerToOneFile() {
try {
GenericApplicationContext context = createContext();
BatchJobDescriptionAdapter bjda = context.getBean(BatchJobDescriptionAdapter.class);
bjda.setLogJobToFile(false);
} catch (Exception e) {
e.printStackTrace();
}
}
private static ExitStatus installComponents(CommandLine parsedArgs) {
String[] components = parsedArgs.getOptionValues("i");
String currentComp = null;
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
for (String comp : components) {
currentComp = comp;
Long jobStartStatus = Long.valueOf(-1);
if(!FILESYSTEM) {
logger.info("EXECUTING: install job from database");
jobStartStatus = componentApi.executeInstall(comp);
} else {
// disable mondrian step
logger.info("EXECUTING: install job from filesystem for " + currentComp);
jobStartStatus = componentApi.executeInstallForQAMuster(comp);
logger.info("EXECUTING: workaround upgrade job from filesystem for " + currentComp);
jobStartStatus = componentApi.executeUpgradeForQAMuster(comp);
}
handleStartResult(jobStartStatus, componentApi);
if(comp.equals("kern") && FILESYSTEM) {
DataSource dataSource = context.getBean(His1DataSources.class).get("eduetl");
SuperXManager.setWebInfFilePath(dataSource);
}
}
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
} catch (Exception e) {
handleJobException(e, currentComp);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static ExitStatus reloadModule() {
try (GenericApplicationContext context = createContext()){
initSxPools();
initSuperXManager();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
componentApi.writeJobsToDb();
} catch (Exception e) {
logger.error("Error reloading modules:", e);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static void printHelp(Options options) {
HelpFormatter help = new HelpFormatter();
help.printHelp(HELP_STRING, options);
}
private static void printInstallableJobs() {
try (GenericApplicationContext context = createContext()) {
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
List<Component> installJobs = componentApi.getInstallJobs();
for (Component comp : installJobs) {
printDetails(comp);
System.out.println();
}
}
}
private static void printEtlJobs() {
try (GenericApplicationContext context = createContext()) {
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
List<Component> etlJobs = componentApi.getEtlJobs();
for (Component etlJob : etlJobs) {
printDetails(etlJob);
System.out.println();
}
}
}
private static void printDetails(Component comp) {
System.out.println("abbrev: " + comp.getAbbreviation());
System.out.println("name: " + comp.getName());
System.out.println("database: " + comp.getDatabase());
System.out.println("systeminfo_id: " + comp.getSysteminfoId());
System.out.println("installed: " + comp.isInstalled());
}
private static ExitStatus deinstallComponent(CommandLine parsedArgs) {
String comp = parsedArgs.getOptionValue("d");
System.out.println(comp);
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
Long jobStartStatus = componentApi.executeUninstall(comp);
handleStartResult(jobStartStatus, componentApi);
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
} catch (Exception e) {
handleJobException(e, comp);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static ExitStatus upgradeComponents(CommandLine parsedArgs) {
String[] components = parsedArgs.getOptionValues("u");
String currentComp = null;
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
for (String comp : components) {
currentComp = comp;
Long jobStartStatus = componentApi.executeUpgrade(comp);
if (jobStartStatus.longValue() == -1) {
logger.warn(comp + " not installed. Skipping upgrade.");
continue;
}
handleStartResult(jobStartStatus, componentApi);
}
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
} catch (Exception e) {
handleJobException(e, currentComp);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static ExitStatus installFunctions(CommandLine parsedArgs) {
String[] components = parsedArgs.getOptionValues("if");
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
// No components given: install
// functions for all installed components
if (components.length == 1 && "all".equals(components[0])) {
List<Component> installedComponents = componentApi.getInstallJobs();
components = installedComponents.stream().map(
c -> c.getAbbreviation()).collect(Collectors.toList()).toArray(new String[] {});
}
boolean exitFailure = false;
for (String comp : components) {
exitFailure = componentApi.installModuleFunctions(comp);
}
if(exitFailure) {
logger.error(("Beim Ausführen einer Aktion ist ein Fehler aufgetreten."));
return ExitStatus.FAILURE;
}
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static ExitStatus upgradeAll() {
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
Long jobStartStatus = componentApi.executeUpgradeAll();
handleStartResult(jobStartStatus, componentApi);
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
} catch (Exception e) {
handleJobException(e, null);
return ExitStatus.FAILURE;
}
return ExitStatus.FAILURE;
}
private static ExitStatus etlJobs(CommandLine parsedArgs) {
String[] jobIds = parsedArgs.getOptionValues("e");
String currentJobId = null;
try (GenericApplicationContext context = createContext()) {
initSxPools();
EtlJobApi componentApi = context.getBean(EtlJobApi.class);
Long jobStartStatus;
for (String jobId : jobIds) {
try {
currentJobId = jobId;
if (isHauptladeroutine(jobId, componentApi)) {
jobStartStatus = componentApi.complete(jobId);
} else if (isLoadTransform(jobId, componentApi)) {
jobStartStatus = componentApi.load(jobId);
} else {
jobStartStatus = componentApi.executeJob(null, jobId);
}
handleStartResult(jobStartStatus, componentApi);
} catch (Exception e) {
logger.error("ERROR executing job " + jobId, e);
}
}
} catch (BeansException be) {
handleBeansException(be);
return ExitStatus.FAILURE;
} catch (Exception e) {
handleJobException(e, currentJobId);
return ExitStatus.FAILURE;
}
return ExitStatus.SUCCESS;
}
private static void handleStartResult(Long jobStartStatus, EtlJobApi componentApi) {
if (jobStartStatus.intValue() == -1) {
System.err.println("Aktion konnte nicht gestartet werden: Es läuft bereits eine Aktion");
}
try {
JobExecutionStatus es = componentApi.getStatus(jobStartStatus);
if ("FAILED".equals(es.exitStatus.getExitCode())) {
EtlJobApi.outputErrorSummary(es, System.err);
System.exit(1);
}
} catch (Exception e) {
System.err.println(("Beim Ausführen der Aktion ist ein Fehler aufgetreten:"));
e.printStackTrace();
System.exit(1);
}
}
private static void handleJobException(Exception e, String jobName) {
System.err.println("error while executing the job '" + jobName + "'");
e.printStackTrace();
}
private static void handleBeansException(BeansException be) {
System.err.println("configuration error or error with resolving the bean '" + EtlJobApi.class.getCanonicalName() + "'");
be.printStackTrace();
}
private static boolean isHauptladeroutine(String comp, EtlJobApi etlJob) {
Component comp_meta = etlJob.getComponentAndConnectorDetails(comp);
return comp_meta != null && comp_meta.isDatabaseConnected();
}
private static boolean isLoadTransform(String comp, EtlJobApi etlJob) {
Component comp_meta = etlJob.getComponentAndConnectorDetails(comp);
return comp_meta !=null && !comp_meta.isDatabaseConnected();
}
private static void printAllJobs() {
try (GenericApplicationContext context = createContext()) {
EtlJobApi etlJob = context.getBean(EtlJobApi.class);
List<ContainerNode> allJobs = etlJob.getAllJobs();
for (ContainerNode cn : allJobs) {
printDetails(cn);
System.out.println();
}
}
}
private static GenericApplicationContext createContext() {
/*
* https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/AnnotationConfigApplicationContext.html
* quote:
* "In case of multiple @Configuration classes, @Bean methods defined in later classes will override those defined in earlier classes.
* This can be leveraged to deliberately override certain bean definitions via an extra @Configuration class."
* - so it's alright to override some beans via "CLIConfig"
*/
if (APPLICATION_CONTEXT == null) {
APPLICATION_CONTEXT = new AnnotationConfigApplicationContext(BatchConfig.class, DataJdbcConfiguration.class, CLIConfig.class, ServiceConfig.class);
HisInOneConfiguration.configSuperXDbformsXML("pg", SuperXManager.getWEB_INFPfad() + File.separator + "..");
}
// Override the JobDescriptionSource Bean if the -f flag is passed.
if(FILESYSTEM) {
EtlJobApi etlJob = APPLICATION_CONTEXT.getBean(EtlJobApi.class);
EntityJobDescriptionSource entityJobDescriptionSource = APPLICATION_CONTEXT.getBean(EntityJobDescriptionSource.class);
etlJob.setJobDescriptionSource(entityJobDescriptionSource);
}
return APPLICATION_CONTEXT;
}
private static void printDetails(ContainerNode cn) {
System.out.println("id: " + cn.id);
System.out.println("name: " + cn.name);
System.out.println("systeminfo_id: " + cn.systemInfoId);
System.out.println("tid: " + cn.tid);
System.out.println("type: " + cn.type);
}
private static Options createOptions() {
Options options = new Options();
Option opt;
opt = new Option("h", "help", false, "get help");
options.addOption(opt);
opt = new Option("f", "filesystem", false, "load jobs from filesystem");
options.addOption(opt);
opt = new Option("s", "setup", false, "setup minimal list of tables necessary for kern install");
options.addOption(opt);
opt = new Option("m", "fail-on-missing-script", false, "Fail if a referenced SQL script is missing during execution");
options.addOption(opt);
opt = new Option("la", "list-all", false, "list all available components");
options.addOption(opt);
opt = new Option("li", "list-installables", false, "list all installable components");
options.addOption(opt);
opt = new Option("le", "list-etl", false, "list all etl components");
options.addOption(opt);
opt = new Option("i", "install", true, "install components");
opt.setArgs(Option.UNLIMITED_VALUES);
options.addOption(opt);
opt = new Option("if", "install-functions", true, "install database functions for components (use 'all' to install functions for all installed components)");
options.addOption(opt);
opt.setArgs(Option.UNLIMITED_VALUES);
opt = new Option("d", "deinstall", true, "de-/uninstall component");
options.addOption(opt);
opt = new Option("u", "upgrade", true, "upgrade components");
opt.setArgs(Option.UNLIMITED_VALUES);
options.addOption(opt);
opt = new Option("ua", "upgrade-all", false, "upgrade all installed components");
options.addOption(opt);
opt = new Option("e", "etl", true, "run etl jobs");
opt.setArgs(Option.UNLIMITED_VALUES);
options.addOption(opt);
opt = new Option("r", "reload-modules", false, "reload modules");
options.addOption(opt);
//opt = new Option("re", "reload-module-etl", true, "reload module etl");
//options.addOption(opt);
opt = new Option("db", "database", true, "database system");
options.addOption(opt);
opt = new Option("lg", "log-to-stdout", false, "log only to stdout and not to individual job files");
options.addOption(opt);
return options;
}
private static CommandLine parseArgs(String[] args, Options options) {
CommandLineParser parser = new GnuParser();
try {
return parser.parse(options, args, false);
} catch (ParseException e) {
System.out.println("error while reading the command line parameters:");
e.printStackTrace();
System.exit(1);
}
return null;
}
// some actions require the SuperXManager, this is the place for initializing its static class attributes when needed
private static void initSuperXManager() {
try {
SuperXManager.setWEB_INFPfad(PathAndFileUtils.getWebinfPath());
SuperXManager.setModuleDir(PathAndFileUtils.getWebinfPath() + File.separator + PathAndFileUtils.MODULE_PATH);
} catch(Exception e) {
System.out.println("error while initialising the SuperXManger:");
e.printStackTrace();
System.exit(1);
}
}
// sxPools need to be initialized, because spring batch ETL uses them to look up the database connections
private static void initSxPools() {
try {
List<String> mandantenNamen = new LinkedList<String>();
mandantenNamen.add(DEFAULT_MANDANTEN_ID);
SxPools.closeAll();
SxPools.init(mandantenNamen);
SxPools.get(DEFAULT_MANDANTEN_ID).init();
SxPools.get(DEFAULT_MANDANTEN_ID).initLogging(true, Level.DEBUG);
// also init kettle env, set plugin dir
SuperXManager.initKettleEnv(APPLICATION_CONTEXT);
} catch (Exception e) {
System.out.println("error while initialising the SuperX pools:");
e.printStackTrace();
System.exit(1);
}
}
}