/*
* Copyright 2015-2016 OpenCB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opencb.opencga.app.cli;
import com.beust.jcommander.JCommander;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.RollingFileAppender;
import org.opencb.commons.utils.FileUtils;
import org.opencb.opencga.catalog.config.Configuration;
import org.opencb.opencga.client.config.ClientConfiguration;
import org.opencb.opencga.storage.core.config.StorageConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* Created by imedina on 19/04/16.
*/
public abstract class CommandExecutor {
protected String logLevel;
@Deprecated
protected String logFile;
protected boolean verbose;
protected String appHome;
protected String conf;
@Deprecated
protected String userId;
@Deprecated
protected String sessionId;
protected CliSession cliSession;
protected Configuration configuration;
protected StorageConfiguration storageConfiguration;
protected ClientConfiguration clientConfiguration;
protected GeneralCliOptions.CommonCommandOptions options;
protected Logger logger;
private Logger privateLogger;
private static final String SESSION_FILENAME = "session.json";
public CommandExecutor(GeneralCliOptions.CommonCommandOptions options) {
this(options, false);
}
public CommandExecutor(GeneralCliOptions.CommonCommandOptions options, boolean loadClientConfiguration) {
this.options = options;
init(options.logLevel, options.verbose, options.conf, loadClientConfiguration);
}
@Deprecated
public CommandExecutor(String logLevel, boolean verbose, String conf) {
init(logLevel, verbose, conf, true);
}
// protected void init(GeneralCliOptions.CommonCommandOptions options) {
// init(options.logLevel, options.verbose, options.conf);
// }
protected void init(String logLevel, boolean verbose, String conf, boolean loadClientConfiguration) {
this.logLevel = logLevel;
this.verbose = verbose;
this.conf = conf;
/**
* System property 'app.home' is automatically set up in opencga.sh. If by any reason
* this is 'null' then OPENCGA_HOME environment variable is used instead.
*/
this.appHome = System.getProperty("app.home", System.getenv("OPENCGA_HOME"));
if (StringUtils.isEmpty(conf)) {
this.conf = appHome + "/conf";
}
// At the moment verbose CLI param acts as a debug log level
if (verbose) {
this.logLevel = "debug";
}
// Loggers can be initialized, the configuration happens just below these lines
logger = LoggerFactory.getLogger(this.getClass().toString());
privateLogger = LoggerFactory.getLogger(CommandExecutor.class);
try {
// At the moment this is needed for all three command lines, this might change soon since REST client should not need this one.
loadConfiguration();
// This code assumes general configuration will be always needed and general configuration is overwritten,
// maybe in the near future this should be an if/else.
if (loadClientConfiguration) {
loadClientConfiguration();
if (StringUtils.isNotEmpty(this.clientConfiguration.getLogLevel())) {
this.configuration.setLogLevel(this.clientConfiguration.getLogLevel());
}
if (StringUtils.isNotEmpty(this.clientConfiguration.getLogFile())) {
this.configuration.setLogFile(this.clientConfiguration.getLogFile());
}
}
// Do not change the order here, we can only configure logger after loading the configuration files,
// this still relies on general configuration file.
configureLogger();
// Let's check the session file, maybe the session is still valid
loadCliSessionFile();
privateLogger.debug("CLI session file is: {}", this.cliSession);
if (cliSession != null) {
this.sessionId = cliSession.getSessionId();
this.userId = cliSession.getUserId();
}
} catch (IOException e) {
e.printStackTrace();
}
// Update the timestamp every time one executed command finishes
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
updateCliSessionFileTimestamp();
} catch (IOException e) {
e.printStackTrace();
}
}));
}
@Deprecated
protected String getSessionId(GeneralCliOptions.CommonCommandOptions commonOptions) {
if (StringUtils.isBlank(commonOptions.sessionId)) {
return cliSession.getSessionId();
} else {
return commonOptions.sessionId;
}
}
public abstract void execute() throws Exception;
private void configureLogger() throws IOException {
org.apache.log4j.Logger rootLogger = LogManager.getRootLogger();
// Disable MongoDB useless logging
org.apache.log4j.Logger.getLogger("org.mongodb.driver.cluster").setLevel(Level.WARN);
org.apache.log4j.Logger.getLogger("org.mongodb.driver.connection").setLevel(Level.WARN);
// Command line parameters have preference over configuration file
// We overwrite logLevel configuration param with command line value
if (StringUtils.isNotEmpty(this.logLevel)) {
this.configuration.setLogLevel(this.logLevel);
}
// We overwrite logFile configuration param with command line value
if (StringUtils.isNotEmpty(this.options.logFile)) {
this.configuration.setLogFile(this.options.logFile);
}
// Configure the logger output, this can be the console or a file if provided by CLI or by configuration file
ConsoleAppender stderr = (ConsoleAppender) rootLogger.getAppender("stderr");
if (StringUtils.isEmpty(this.configuration.getLogFile())) {
stderr.setThreshold(Level.toLevel(configuration.getLogLevel(), Level.INFO));
} else {
RollingFileAppender rollingFileAppender = new RollingFileAppender(stderr.getLayout(), this.configuration.getLogFile(), true);
rootLogger.addAppender(rollingFileAppender);
rollingFileAppender.setThreshold(Level.toLevel(configuration.getLogLevel(), Level.INFO));
rollingFileAppender.setMaxFileSize("100MB");
rollingFileAppender.setMaxBackupIndex(10);
}
}
@Deprecated
public boolean loadConfigurations() {
try {
loadConfiguration();
} catch (IOException ex) {
if (getLogger() == null) {
ex.printStackTrace();
} else {
getLogger().error("Error reading OpenCGA Catalog configuration: " + ex.getMessage());
}
return false;
}
try {
loadStorageConfiguration();
} catch (IOException ex) {
if (getLogger() == null) {
ex.printStackTrace();
} else {
getLogger().error("Error reading OpenCGA Storage configuration: " + ex.getMessage());
}
return false;
}
return true;
}
/**
* This method attempts to load general configuration from CLI 'conf' parameter, if not exists then loads JAR storage-configuration.yml.
*
* @throws IOException If any IO problem occurs
*/
public void loadConfiguration() throws IOException {
FileUtils.checkDirectory(Paths.get(this.conf));
// We load configuration file either from app home folder or from the JAR
Path path = Paths.get(this.conf).resolve("configuration.yml");
if (path != null && Files.exists(path)) {
privateLogger.debug("Loading configuration from '{}'", path.toAbsolutePath());
this.configuration = Configuration.load(new FileInputStream(path.toFile()));
} else {
privateLogger.debug("Loading configuration from JAR file");
this.configuration = Configuration
.load(ClientConfiguration.class.getClassLoader().getResourceAsStream("configuration.yml"));
}
}
/**
* This method attempts to first data configuration from CLI parameter, if not present then uses
* the configuration from installation directory, if not exists then loads JAR storage-configuration.yml.
*
* @throws IOException If any IO problem occurs
*/
public void loadClientConfiguration() throws IOException {
// We load configuration file either from app home folder or from the JAR
Path path = Paths.get(this.conf).resolve("client-configuration.yml");
if (path != null && Files.exists(path)) {
privateLogger.debug("Loading configuration from '{}'", path.toAbsolutePath());
this.clientConfiguration = ClientConfiguration.load(new FileInputStream(path.toFile()));
} else {
privateLogger.debug("Loading configuration from JAR file");
this.clientConfiguration = ClientConfiguration
.load(ClientConfiguration.class.getClassLoader().getResourceAsStream("client-configuration.yml"));
}
}
/**
* This method attempts to load storage configuration from CLI 'conf' parameter, if not exists then loads JAR storage-configuration.yml.
*
* @throws IOException If any IO problem occurs
*/
public void loadStorageConfiguration() throws IOException {
FileUtils.checkDirectory(Paths.get(this.conf));
// We load configuration file either from app home folder or from the JAR
Path path = Paths.get(this.conf).resolve("storage-configuration.yml");
if (path != null && Files.exists(path)) {
privateLogger.debug("Loading storage configuration from '{}'", path.toAbsolutePath());
this.storageConfiguration = StorageConfiguration.load(new FileInputStream(path.toFile()));
} else {
privateLogger.debug("Loading storage configuration from JAR file");
this.storageConfiguration = StorageConfiguration
.load(ClientConfiguration.class.getClassLoader().getResourceAsStream("storage-configuration.yml"));
}
}
protected void loadCliSessionFile() throws IOException {
Path sessionPath = Paths.get(System.getProperty("user.home"), ".opencga", SESSION_FILENAME);
if (Files.exists(sessionPath)) {
this.cliSession = new ObjectMapper().readValue(sessionPath.toFile(), CliSession.class);
}
}
protected void saveCliSessionFile(String user, String session, Map<String, List<String>> studies) throws IOException {
Path sessionPath = Paths.get(System.getProperty("user.home"), ".opencga");
// check if ~/.opencga folder exists
if (!Files.exists(sessionPath)) {
Files.createDirectory(sessionPath);
}
sessionPath = sessionPath.resolve(SESSION_FILENAME);
new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(sessionPath.toFile(), new CliSession(user, session, studies));
}
protected void updateCliSessionFileTimestamp() throws IOException {
Path sessionPath = Paths.get(System.getProperty("user.home"), ".opencga", SESSION_FILENAME);
if (Files.exists(sessionPath)) {
ObjectMapper objectMapper = new ObjectMapper();
CliSession cliSession = objectMapper.readValue(sessionPath.toFile(), CliSession.class);
cliSession.setTimestamp(System.currentTimeMillis());
objectMapper.writerWithDefaultPrettyPrinter().writeValue(sessionPath.toFile(), cliSession);
}
}
protected void logoutCliSessionFile() throws IOException {
Path sessionPath = Paths.get(System.getProperty("user.home"), ".opencga", SESSION_FILENAME);
if (Files.exists(sessionPath)) {
ObjectMapper objectMapper = new ObjectMapper();
CliSession cliSession = objectMapper.readValue(sessionPath.toFile(), CliSession.class);
cliSession.setLogout(LocalDateTime.now().toString());
cliSession.setProjectsAndStudies(null);
objectMapper.writerWithDefaultPrettyPrinter().writeValue(sessionPath.toFile(), cliSession);
}
}
protected static String getParsedSubCommand(JCommander jCommander) {
String parsedCommand = jCommander.getParsedCommand();
if (jCommander.getCommands().containsKey(parsedCommand)) {
String subCommand = jCommander.getCommands().get(parsedCommand).getParsedCommand();
return subCommand != null ? subCommand: "";
} else {
return "";
}
}
public Configuration getConfiguration() {
return configuration;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
public String getLogLevel() {
return logLevel;
}
public String getLogFile() {
return logFile;
}
public void setLogFile(String logFile) {
this.logFile = logFile;
}
public boolean isVerbose() {
return verbose;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public String getConf() {
return conf;
}
public void setConf(String conf) {
this.conf = conf;
}
public Logger getLogger() {
return logger;
}
}