package org.gbif.ipt.config;
import org.gbif.ipt.action.BaseAction;
import org.gbif.ipt.config.AppConfig.REGISTRY_TYPE;
import org.gbif.ipt.model.Extension;
import org.gbif.ipt.model.Organisation;
import org.gbif.ipt.model.User;
import org.gbif.ipt.model.User.Role;
import org.gbif.ipt.service.AlreadyExistingException;
import org.gbif.ipt.service.InvalidConfigException;
import org.gbif.ipt.service.InvalidConfigException.TYPE;
import org.gbif.ipt.service.admin.ConfigManager;
import org.gbif.ipt.service.admin.ExtensionManager;
import org.gbif.ipt.service.admin.RegistrationManager;
import org.gbif.ipt.service.admin.UserAccountManager;
import org.gbif.ipt.service.admin.VocabulariesManager;
import org.gbif.ipt.struts2.SimpleTextProvider;
import org.gbif.ipt.utils.URLUtils;
import org.gbif.ipt.validation.UserValidator;
import org.gbif.utils.HttpUtil;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
/**
* The Action responsible for all user input relating to the IPT configuration.
*/
public class SetupAction extends BaseAction {
// logging
private static final Logger LOG = Logger.getLogger(SetupAction.class);
private static final long serialVersionUID = 4726973323043063968L;
private final ConfigManager configManager;
private final UserAccountManager userManager;
private final DataDir dataDir;
private final ExtensionManager extensionManager;
private final VocabulariesManager vocabulariesManager;
private final UserValidator userValidation = new UserValidator();
// action attributes to be set
protected String dataDirPath;
protected boolean readDisclaimer;
protected User user = new User();
private String password2;
protected String modeSelected;
protected String baseURL;
protected String proxy;
// can't pass a literal boolean to ftl, using int instead...
protected Integer ignoreUserValidation = 0;
private boolean setup2 = false;
private final HttpUtil httpUtil;
private static final String MODE_DEVELOPMENT = "Test";
private static final String MODE_PRODUCTION = "Production";
private static final List<String> MODES = ImmutableList.of(MODE_DEVELOPMENT, MODE_PRODUCTION);
@Inject
public SetupAction(SimpleTextProvider textProvider, AppConfig cfg, RegistrationManager regManager,
ConfigManager configManager, UserAccountManager userManager, DataDir dataDir,
ExtensionManager extensionManager, DefaultHttpClient client, VocabulariesManager vocabulariesManager) {
super(textProvider, cfg, regManager);
this.configManager = configManager;
this.userManager = userManager;
this.dataDir = dataDir;
this.extensionManager = extensionManager;
this.httpUtil = new HttpUtil(client);
this.vocabulariesManager = vocabulariesManager;
}
public List<String> getModes() {
return MODES;
}
public String continueHome() {
return SUCCESS;
}
/**
* Tries to guess the current baseURL on the running server from the context
*
* @return baseURL as string
*/
public String findBaseURL() {
// try to detect the baseURL if not configured yet!
String appBase = req.getRequestURL().toString().replaceAll(req.getServletPath(), "");
LOG.info("Auto-Detected IPT BaseURL=" + appBase);
return appBase;
}
@Override
public String getBaseURL() {
if (Strings.isNullOrEmpty(baseURL)) {
// try to detect default values if not yet configured
if (StringUtils.trimToNull(cfg.getBaseUrl()) == null) {
baseURL = findBaseURL();
} else {
baseURL = cfg.getBaseUrl();
}
}
return baseURL;
}
public String getDataDirPath() {
return dataDirPath;
}
public Integer getIgnoreUserValidation() {
return this.ignoreUserValidation;
}
public String getPassword2() {
return password2;
}
public String getProxy() {
return proxy;
}
public User getUser() {
return user;
}
/**
* If the config is in debug mode, then production settings are not possible.
*
* @return true if production setting is allowed
*/
public boolean isProductionSettingAllowed() {
return !cfg.debug();
}
public void setBaseURL(String baseUrlVerbatim) {
this.baseURL = baseUrlVerbatim;
}
public void setDataDirPath(String dataDirPath) {
this.dataDirPath = dataDirPath;
}
public void setIgnoreUserValidation(Integer ignoreUserValidation) {
this.ignoreUserValidation = ignoreUserValidation;
}
public void setPassword2(String password2) {
this.password2 = password2;
}
public void setProxy(String proxy) {
this.proxy = proxy;
}
public void setSetup2(boolean setup2) {
this.setup2 = setup2;
}
/**
* Method called when setting up the IPT for the very first time. There might not even be a logged in user, be
* careful
* to not require an admin!
*/
public String setup() {
if (isHttpPost() && dataDirPath != null) {
// since IPT v2.2, user must check that they have read and understood disclaimer
if (!readDisclaimer) {
addFieldError("readDisclaimer", getText("admin.config.setup.read.error"));
return INPUT;
}
File dd = new File(dataDirPath.trim());
try {
if (dd.isAbsolute()) {
boolean created = configManager.setDataDir(dd);
if (created) {
addActionMessage(getText("admin.config.setup.datadir.created"));
} else {
addActionMessage(getText("admin.config.setup.datadir.reused"));
}
} else {
addActionError(getText("admin.config.setup.datadir.absolute", new String[] {dataDirPath}));
}
} catch (InvalidConfigException e) {
LOG.warn("Failed to setup datadir: " + e.getMessage(), e);
if (e.getType() == InvalidConfigException.TYPE.NON_WRITABLE_DATA_DIR) {
addActionError(getText("admin.config.setup.datadir.writable", new String[] {dataDirPath}));
} else {
addActionError(getText("admin.config.setup.datadir.error"));
}
}
}
if (dataDir.isConfigured()) {
// the data dir is already/now configured, skip the first setup step
return SUCCESS;
}
return INPUT;
}
/**
* Method called when setting up the IPT for the very first time. The admin user, mode, base URL, and proxy are set.
*
* @return Struts Action String
*/
public String setup2() {
// first check if a data directory exists.
if (!dataDir.isConfigured()) {
addActionWarning(getText("admin.config.setup2.datadir.notExist"));
return ERROR;
}
// second check if the selected datadir contains an admin user already
if (configManager.setupComplete()) {
if (configManager.isBaseURLValid()) {
addActionMessage(getText("admin.config.setup2.existingFound"));
return SUCCESS;
} else if (!isHttpPost()) {
// the only way here is if this is a new deploy over an old data dir and the old base URL is bad
baseURL = cfg.getBaseUrl();
proxy = cfg.getProxy();
List<User> admins = userManager.list(User.Role.Admin);
if (admins != null && !admins.isEmpty()) {
user = admins.get(0);
}
ignoreUserValidation = 1;
addFieldError("baseURL", getText("admin.config.baseUrl.inaccessible"));
}
}
if (isHttpPost()) {
// we have submitted the form
try {
boolean gotValidUser = false;
URL burl = null;
if (ignoreUserValidation == 0) {
user.setRole(Role.Admin);
// do user validation, but don't create user yet
gotValidUser = userValidation.validate(this, user);
try {
burl = new URL(baseURL);
} catch (MalformedURLException e) {
// checked in validate() already
}
if (getModeSelected() == null) {
addFieldError("modeSelected", getText("admin.config.setup2.nomode"));
return INPUT;
}
// set IPT type: registry URL
if (getModeSelected().equalsIgnoreCase(MODE_PRODUCTION) && !cfg.devMode()) {
if (URLUtils.isLocalhost(burl)) {
addFieldError("baseURL", getText("admin.config.baseUrl.invalidBaseURL"));
return INPUT;
} else if (URLUtils.isHostName(burl)) {
// warn the base URL is same as machine name so user ensures it is visible on the Internet
LOG.info("Machine name used in base URL");
addActionWarning(getText("admin.config.baseUrl.sameHostName"));
}
cfg.setRegistryType(REGISTRY_TYPE.PRODUCTION);
LOG.info("Production mode has been selected");
} else {
cfg.setRegistryType(REGISTRY_TYPE.DEVELOPMENT);
LOG.info("Test mode has been selected");
}
}
// set baseURL, this has to be before the validation with the proxy
// will try to get local CSS file with this base URL and if it fails throws an InvalidConfigException
configManager.setBaseUrl(burl);
// set proxy
try {
configManager.setProxy(proxy);
} catch (InvalidConfigException e) {
addFieldError("proxy", getText(e.getMessage()) + " " + proxy);
return INPUT;
}
// save config
configManager.saveConfig();
// everything else is valid, now create the user
if (ignoreUserValidation == 0 && gotValidUser) {
// confirm password
userManager.create(user);
user.setLastLoginToNow();
userManager.save();
// login as new admin
session.put(Constants.SESSION_USER, user);
}
addActionMessage(getText("admin.config.setup2.success"));
addActionMessage(getText("admin.config.setup2.next"));
userManager.setSetupUser(user);
return SUCCESS;
} catch (IOException e) {
LOG.error(e);
addActionError(getText("admin.config.setup2.failed", new String[] {e.getMessage()}));
} catch (AlreadyExistingException e) {
addFieldError("user.email", getText("admin.config.setup2.nonadmin"));
} catch (InvalidConfigException e) {
if (e.getType() == TYPE.INACCESSIBLE_BASE_URL) {
addFieldError("baseURL", getText("admin.config.baseUrl.inaccessible") + " " + baseURL);
} else {
LOG.error(e);
addActionError(
getTextWithDynamicArgs("admin.config.setup2.already.registered", cfg.getRegistryType().toString()));
}
}
}
return INPUT;
}
public String setup3() {
configManager.loadDataDirConfig();
session.put(Constants.SESSION_USER, userManager.getSetupUser());
// install or update latest version of all default vocabularies
try {
vocabulariesManager.installOrUpdateDefaults();
} catch (InvalidConfigException e) {
String msg = getText("admin.vocabulary.couldnt.install.defaults", new String[] {e.getMessage()});
LOG.error(msg, e);
addActionWarning(msg, e);
}
List<Extension> extensions = extensionManager.listCore();
if (extensions.isEmpty()) {
try {
// install core type extensions
extensionManager.installCoreTypes();
} catch (InvalidConfigException e) {
LOG.error(e);
addActionWarning(getText("admin.extension.couldnt.install.coreTypes"), e);
}
}
// install default organisation "No organisation" used to indicate resource has no publishing organisation
if (registrationManager.getIpt() == null || getDefaultOrganisation() == null) {
try {
registrationManager.addAssociatedOrganisation(createDefaultOrganisation());
registrationManager.save();
} catch (Exception e) {
LOG.error(e);
addActionWarning(getText("admin.error.invalidConfiguration", new String[] {e.getMessage()}), e);
}
}
return INPUT;
}
public void setUser(User user) {
this.user = user;
}
@Override
public void validate() {
if (setup2) {
if (ignoreUserValidation == 0 && user != null) {
userValidation.validate(this, user);
if (StringUtils.trimToNull(user.getPassword()) != null && !user.getPassword().equals(password2)) {
addFieldError("password2", getText("validation.password2.wrong"));
}
}
if (StringUtils.trimToNull(baseURL) == null) {
addFieldError("baseURL", getText("validation.baseURL.required"));
} else if (!URLUtils.isURLValid(baseURL)) {
addFieldError("baseURL", getText("validation.baseURL.invalid") + " " + baseURL);
} else {
try {
new URL(baseURL);
} catch (MalformedURLException e) {
addFieldError("baseURL", getText("validation.baseURL.invalid") + " " + baseURL);
}
}
if (StringUtils.trimToNull(proxy) != null) {
if (!URLUtils.isURLValid(proxy)) {
addFieldError("proxy", getText("admin.config.proxy.error") + " " + proxy);
} else {
try {
HttpHost host = URLUtils.getHost(proxy);
if (!httpUtil.verifyHost(host)) {
addFieldError("proxy", getText("admin.config.error.connectionRefused") + " " + proxy);
}
} catch (MalformedURLException e) {
addFieldError("proxy", getText("admin.config.error.invalidProxyURL") + " " + proxy);
}
}
}
}
}
public String getModeSelected() {
if (cfg != null && cfg.devMode()) {
return MODE_DEVELOPMENT;
}
return modeSelected;
}
/**
* The mode the IPT will run in: test or production.
*
* @param modeSelected mode that has been selected to run the IPT in
*/
public void setModeSelected(String modeSelected) {
this.modeSelected = modeSelected;
}
/**
* @return true if the user has checked that they have read and understood the disclaimer, false otherwise
*/
public boolean isReadDisclaimer() {
return readDisclaimer;
}
public void setReadDisclaimer(boolean readDisclaimer) {
this.readDisclaimer = readDisclaimer;
}
/**
* Construct and return a default organisation. This can be used for example, to populate the IPT with at least
* one organisation
*
* @return default organisation
*/
private Organisation createDefaultOrganisation() {
Organisation organisation = new Organisation();
String name = getText("eml.publishingOrganisation.none");
organisation.setName(name);
organisation.setAlias(name);
organisation.setCanHost(true);
organisation.setDescription("Installed by default, used to indicate resource is not published by any organisation");
organisation.setKey(Constants.DEFAULT_ORG_KEY.toString());
organisation.setPassword("password");
return organisation;
}
}