blob: 0f3af535bdd09f152da28862b3a9228cde876e67 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.openmeetings.cli;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.cli.PosixParser;
import org.apache.openjpa.jdbc.meta.MappingTool;
import org.openmeetings.app.OpenmeetingsVariables;
import org.openmeetings.app.data.file.FileUtils;
import org.openmeetings.app.documents.InstallationDocumentHandler;
import org.openmeetings.app.installation.ImportInitvalues;
import org.openmeetings.app.installation.InstallationConfig;
import org.openmeetings.app.remote.red5.ScopeApplicationAdapter;
import org.openmeetings.servlet.outputhandler.BackupExport;
import org.openmeetings.servlet.outputhandler.BackupImportController;
import org.openmeetings.utils.ImportHelper;
import org.openmeetings.utils.OMContextListener;
import org.openmeetings.utils.mail.MailUtil;
import org.openmeetings.utils.math.CalendarPatterns;
import org.quartz.SchedulerException;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
public class Admin {
private static final Logger log = Red5LoggerFactory.getLogger(Admin.class);
private boolean verbose = false;
private InstallationConfig cfg = null;
private Options opts = null;
private CommandLine cmdl = null;
private File omHome = null;
private ClassPathXmlApplicationContext ctx = null;
private Admin() {
cfg = new InstallationConfig();
opts = buildOptions();
}
private Options buildOptions() {
Options options = new Options();
OptionGroup group = new OptionGroup()
.addOption(new OmOption("h", 0, "h", "help", false, "prints this message"))
.addOption(new OmOption("b", 1, "b", "backup", false, "Backups OM"))
.addOption(new OmOption("r", 2, "r", "restore", false, "Restores OM"))
.addOption(new OmOption("i", 3, "i", "install", false, "Fill DB table, and make OM usable"))
.addOption(new OmOption("f", 4, "f", "files", false, "File operations - statictics/cleanup"));
group.setRequired(true);
options.addOptionGroup(group);
//general
options.addOption(new OmOption(null, "v", "verbose", false, "verbose error messages"));
//backup/restore
options.addOption(new OmOption("b", null, "exclude-files", false, "should backup exclude files [default: include]", true));
options.addOption(new OmOption("b,r,i", "file", null, true, "file used for backup/restore/install", "b"));
//install
options.addOption(new OmOption("i", "user", null, true, "Login name of the default user, minimum " + InstallationConfig.USER_LOGIN_MINIMUM_LENGTH + " characters (mutually exclusive with 'file')"));
options.addOption(new OmOption("i", "email", null, true, "Email of the default user (mutually exclusive with 'file')"));
options.addOption(new OmOption("i", "group", null, true, "The name of the default user group (mutually exclusive with 'file')"));
options.addOption(new OmOption("i", "tz", null, true, "Default server time zone, and time zone for the selected user (mutually exclusive with 'file')"));
options.addOption(new OmOption("i", null, "password", true, "Password of the default user, minimum " + InstallationConfig.USER_LOGIN_MINIMUM_LENGTH + " characters (will be prompted if not set)", true));
options.addOption(new OmOption("i", null, "system-email-address", true, "System e-mail address [default: " + cfg.mailReferer + "]", true));
options.addOption(new OmOption("i", null, "smtp-server", true, "SMTP server for outgoing e-mails [default: " + cfg.smtpServer + "]", true));
options.addOption(new OmOption("i", null, "smtp-port", true, "SMTP server for outgoing e-mails [default: " + cfg.smtpPort + "]", true));
options.addOption(new OmOption("i", null, "email-auth-user", true, "Email auth username (anonymous connection will be used if not set)", true));
options.addOption(new OmOption("i", null, "email-auth-pass", true, "Email auth password (anonymous connection will be used if not set)", true));
options.addOption(new OmOption("i", null, "email-use-tls", false, "Is secure e-mail connection [default: no]", true));
options.addOption(new OmOption("i", null, "skip-default-rooms", false, "Do not create default rooms [created by default]", true));
options.addOption(new OmOption("i", null, "disable-frontend-register", false, "Do not allow front end register [allowed by default]", true));
options.addOption(new OmOption("i", null, "db-type", true, "The type of the DB to be used", true));
options.addOption(new OmOption("i", null, "db-host", true, "DNS name or IP address of database", true));
options.addOption(new OmOption("i", null, "db-port", true, "Database port", true));
options.addOption(new OmOption("i", null, "db-name", true, "The name of Openmeetings database", true));
options.addOption(new OmOption("i", null, "db-user", true, "User with write access to the DB specified", true));
options.addOption(new OmOption("i", null, "db-pass", true, "Password of the user with write access to the DB specified", true));
options.addOption(new OmOption("i", null, "drop", false, "Drop database before installation", true));
options.addOption(new OmOption("i", null, "force", false, "Install without checking the existence of old data in the database.", true));
return options;
}
private enum Command {
install
, backup
, restore
, files
, usage
}
private void usage() {
OmHelpFormatter formatter = new OmHelpFormatter();
formatter.setWidth(100);
formatter.printHelp("admin", opts);
}
private void handleError(String msg, Exception e) {
handleError(msg, e, false);
}
private void handleError(String msg, Exception e, boolean printUsage) {
if (printUsage) {
usage();
}
if (verbose) {
log.error(msg, e);
} else {
log.error(msg + " " + e.getMessage());
}
System.exit(1);
}
private ClassPathXmlApplicationContext getApplicationContext(final String ctxName) {
if (ctx == null) {
OMContextListener omcl = new OMContextListener();
omcl.contextInitialized(new ServletContextEvent(new ServletContext() {
public void setAttribute(String arg0, Object arg1) {
}
public void removeAttribute(String arg0) {
}
public void log(String arg0, Throwable arg1) {
}
public void log(Exception arg0, String arg1) {
}
public void log(String arg0) {
}
@SuppressWarnings("rawtypes")
public Enumeration getServlets() {
return null;
}
@SuppressWarnings("rawtypes")
public Enumeration getServletNames() {
return null;
}
public String getServletContextName() {
return null;
}
public Servlet getServlet(String arg0) throws ServletException {
return null;
}
public String getServerInfo() {
return null;
}
@SuppressWarnings("rawtypes")
public Set getResourcePaths(String arg0) {
return null;
}
public InputStream getResourceAsStream(String arg0) {
return null;
}
public URL getResource(String arg0) throws MalformedURLException {
return null;
}
public RequestDispatcher getRequestDispatcher(String arg0) {
return null;
}
public String getRealPath(String arg0) {
return null;
}
public RequestDispatcher getNamedDispatcher(String arg0) {
return null;
}
public int getMinorVersion() {
return 0;
}
public String getMimeType(String arg0) {
return null;
}
public int getMajorVersion() {
return 0;
}
@SuppressWarnings("rawtypes")
public Enumeration getInitParameterNames() {
return null;
}
public String getInitParameter(String arg0) {
return null;
}
public String getContextPath() {
return ctxName;
}
public ServletContext getContext(String arg0) {
return null;
}
@SuppressWarnings("rawtypes")
public Enumeration getAttributeNames() {
return null;
}
public Object getAttribute(String arg0) {
return null;
}
}));
try {
ctx = new ClassPathXmlApplicationContext("openmeetings-applicationContext.xml");
} catch (Exception e) {
handleError("Unable to obtain application context", e);
}
}
return ctx;
}
private void process(String[] args) {
String ctxName = System.getProperty("context", "openmeetings");
File home = new File(System.getenv("RED5_HOME"));
omHome = new File(new File(home, "webapps"), ctxName);
File omUploadTemp = new File(omHome, OpenmeetingsVariables.UPLOAD_TEMP_DIR);
Parser parser = new PosixParser();
try {
cmdl = parser.parse(opts, args);
} catch (ParseException e) {
System.out.println(e.getMessage());
usage();
System.exit(1);
}
verbose = cmdl.hasOption('v');
Command cmd = Command.usage;
if (cmdl.hasOption('i')) {
cmd = Command.install;
} else if (cmdl.hasOption('b')) {
cmd = Command.backup;
} else if (cmdl.hasOption('r')) {
cmd = Command.restore;
} else if (cmdl.hasOption('f')) {
cmd = Command.files;
}
String file = cmdl.getOptionValue("file", "");
switch(cmd) {
case install:
try {
if (cmdl.hasOption("file") && (cmdl.hasOption("user") || cmdl.hasOption("email") || cmdl.hasOption("group"))) {
System.out.println("Please specify even 'file' option or 'admin user'.");
System.exit(1);
}
boolean force = cmdl.hasOption("force");
if (cmdl.hasOption("skip-default-rooms")) {
cfg.createDefaultRooms = "0";
}
if (cmdl.hasOption("disable-frontend-register")) {
cfg.allowFrontendRegister = "0";
}
if (cmdl.hasOption("system-email-address")) {
cfg.mailReferer = cmdl.getOptionValue("system-email-address");
}
if (cmdl.hasOption("smtp-server")) {
cfg.smtpServer = cmdl.getOptionValue("smtp-server");
}
if (cmdl.hasOption("smtp-port")) {
cfg.smtpPort = cmdl.getOptionValue("smtp-port");
}
if (cmdl.hasOption("email-auth-user")) {
cfg.mailAuthName = cmdl.getOptionValue("email-auth-user");
}
if (cmdl.hasOption("email-auth-pass")) {
cfg.mailAuthPass = cmdl.getOptionValue("email-auth-pass");
}
if (cmdl.hasOption("email-use-tls")) {
cfg.mailUseTls = "1";
}
String langPath = new File(omHome, ImportInitvalues.languageFolderName).getAbsolutePath(); //FIXME need to be moved to helper
ConnectionProperties connectionProperties = new ConnectionProperties();
File conf = new File(omHome, "WEB-INF/classes/META-INF/persistence.xml");
if (!conf.exists() || cmdl.hasOption("db-type") || cmdl.hasOption("db-host") || cmdl.hasOption("db-port") || cmdl.hasOption("db-name") || cmdl.hasOption("db-user") || cmdl.hasOption("db-pass")) {
String dbType = cmdl.getOptionValue("db-type", "derby");
File srcConf = new File(omHome, "WEB-INF/classes/META-INF/" + dbType + "_persistence.xml");
ConnectionPropertiesPatcher.getPatcher(dbType).patch(
srcConf
, conf
, cmdl.getOptionValue("db-host", "localhost")
, cmdl.getOptionValue("db-port", null)
, cmdl.getOptionValue("db-name", null)
, cmdl.getOptionValue("db-user", null)
, cmdl.getOptionValue("db-pass", null)
, connectionProperties
);
} else {
//get properties from existent persistence.xml
connectionProperties = ConnectionPropertiesPatcher.getConnectionProperties(conf);
}
if (cmdl.hasOption("file")) {
File backup = checkRestoreFile(file);
dropDB(connectionProperties);
shutdownScheduledJobs(ctxName);
ImportInitvalues importInit = getApplicationContext(ctxName).getBean(ImportInitvalues.class);
importInit.loadSystem(langPath, cfg, force);
restoreOm(ctxName, backup);
} else {
AdminUserDetails admin = checkAdminDetails(ctxName, langPath);
dropDB(connectionProperties);
shutdownScheduledJobs(ctxName);
ImportInitvalues importInit = getApplicationContext(ctxName).getBean(ImportInitvalues.class);
importInit.loadAll(langPath, cfg, admin.login, admin.pass, admin.email, admin.group, admin.tz, force);
}
File installerFile = new File(new File(omHome, ScopeApplicationAdapter.configDirName), InstallationDocumentHandler.installFileName);
InstallationDocumentHandler.getInstance().createDocument(installerFile.getAbsolutePath(), 3);
} catch(Exception e) {
handleError("Install failed", e);
}
break;
case backup:
try {
if (!cmdl.hasOption("file")) {
file = "backup_" + CalendarPatterns.getTimeForStreamId(new Date()) + ".zip";
System.out.println("File name was not specified, '" + file + "' will be used");
}
boolean includeFiles = Boolean.parseBoolean(cmdl.getOptionValue("exclude-files", "true"));
File backup_dir = new File(omUploadTemp, "" + System.currentTimeMillis());
backup_dir.mkdirs();
shutdownScheduledJobs(ctxName);
BackupExport export = getApplicationContext(ctxName).getBean(BackupExport.class);
export.performExport(file, backup_dir, includeFiles, omHome.getAbsolutePath());
export.deleteDirectory(backup_dir);
backup_dir.delete();
} catch (Exception e) {
handleError("Backup failed", e);
}
break;
case restore:
try {
shutdownScheduledJobs(ctxName);
restoreOm(ctxName, checkRestoreFile(file));
} catch (Exception e) {
handleError("Restore failed", e);
}
break;
case files:
try {
File omUpload = new File(omHome, OpenmeetingsVariables.UPLOAD_DIR);
File omStreams = new File(omHome, OpenmeetingsVariables.STREAMS_DIR);
System.out.println("Temporary upload files allocates: " + FileUtils.getHumanSize(omUploadTemp));
System.out.println("Upload allocates: " + FileUtils.getHumanSize(omUpload));
System.out.println("Recordings allocates: " + FileUtils.getHumanSize(omStreams));
/*
omHome
ClassPathXmlApplicationContext ctx = getApplicationContext(ctxName);
//user pictures
//dist/red5/webapps/openmeetings/upload/profiles/profile_<id> (check if ends with filename)
UsersDaoImpl udao = ctx.getBean(UsersDaoImpl.class);
for (Users u : udao.getAllUsersDeleted()) {
System.out.println("id == " + u.getUser_id() + "; deleted ? " + u.getDeleted() + "; uri -> " + u.getPictureuri());
}
*/
//Upload backup ???
//Upload import ???
//public/private files
//Object: fileexploreritem (filehash == document file/folder)
//webapps/openmeetings/upload/files (check if ends with filename)
//public/private recordings
//Object: flvrecording
//webapps/openmeetings/streams/<room_id>/rec_<id>* -->temporary files
//webapps/openmeetings/streams/hibernate/flvRecording_<id>* -->files
} catch (Exception e) {
handleError("Files failed", e);
}
break;
case usage:
default:
usage();
break;
}
System.out.println("... Done");
System.exit(0);
}
private class AdminUserDetails {
String login = null;
String email = null;
String group = null;
String pass = null;
String tz = null;
}
private AdminUserDetails checkAdminDetails(String ctxName, String langPath) throws Exception {
AdminUserDetails admin = new AdminUserDetails();
admin.login = cmdl.getOptionValue("user");
admin.email = cmdl.getOptionValue("email");
admin.group = cmdl.getOptionValue("group");
if (admin.login == null || admin.login.length() < InstallationConfig.USER_LOGIN_MINIMUM_LENGTH) {
System.out.println("User login was not provided, or too short, should be at least " + InstallationConfig.USER_LOGIN_MINIMUM_LENGTH + " character long.");
System.exit(1);
}
try {
if (!MailUtil.matches(admin.email)) {
throw new AddressException("Invalid address");
}
new InternetAddress(admin.email, true);
} catch (AddressException ae) {
System.out.println("Please provide non-empty valid email: '" + admin.email + "' is not valid.");
System.exit(1);
}
if (admin.group == null || admin.group.length() < 1) {
System.out.println("User group was not provided, or too short, should be at least 1 character long: " + admin.group);
System.exit(1);
}
admin.pass = cmdl.getOptionValue("password");
if (checkPassword(admin.pass)) {
System.out.print("Please enter password for the user '" + admin.login + "':");
admin.pass = new BufferedReader(new InputStreamReader(System.in)).readLine();
if (checkPassword(admin.pass)) {
System.out.println("Password was not provided, or too short, should be at least " + InstallationConfig.USER_PASSWORD_MINIMUM_LENGTH + " character long.");
System.exit(1);
}
}
ImportInitvalues importInit = getApplicationContext(ctxName).getBean(ImportInitvalues.class);
Map<String, String> tzMap = ImportHelper.getAllTimeZones(importInit.getTimeZones(langPath));
admin.tz = null;
if (cmdl.hasOption("tz")) {
admin.tz = cmdl.getOptionValue("tz");
admin.tz = tzMap.containsKey(admin.tz) ? admin.tz : null;
}
if (admin.tz == null) {
System.out.println("Please enter timezone, Possible timezones are:");
for (String tzIcal : tzMap.keySet()) {
System.out.println(String.format("%1$-25s%2$s", "\"" + tzIcal + "\"", tzMap.get(tzIcal)));
}
System.exit(1);
}
return admin;
}
private boolean checkPassword(String pass) {
return (pass == null || pass.length() < InstallationConfig.USER_PASSWORD_MINIMUM_LENGTH);
}
private void shutdownScheduledJobs(String ctxName) throws SchedulerException {
SchedulerFactoryBean sfb = getApplicationContext(ctxName).getBean(SchedulerFactoryBean.class);
sfb.getScheduler().shutdown(false);
}
private void dropDB(ConnectionProperties props) throws Exception {
if(cmdl.hasOption("drop")) {
String[] args = {
"-schemaAction", "retain,drop"
, "-properties", omHome.getAbsolutePath() + "/WEB-INF/classes/META-INF/persistence.xml"
, "-connectionDriverName", props.getDriver()
, "-connectionURL", props.getURL()
, "-connectionUserName", props.getLogin()
, "-connectionPassword", props.getPassword()
, "-ignoreErrors", "true"};
MappingTool.main(args);
}
}
private File checkRestoreFile(String file) {
File backup = new File(file);
if (!cmdl.hasOption("file") || !backup.exists() || !backup.isFile()) {
System.out.println("File should be specified, and point the existent zip file");
usage();
System.exit(1);
}
return backup;
}
private void restoreOm(String ctxName, File backup) {
try {
BackupImportController importCtrl = getApplicationContext(ctxName).getBean(BackupImportController.class);
importCtrl.performImport(new FileInputStream(backup), omHome.getAbsolutePath());
} catch (Exception e) {
handleError("Restore failed", e);
}
}
public static void main(String[] args) {
new Admin().process(args);
}
}