/* * (C) Copyright 2010-2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Julien Carsique * */ package org.nuxeo.launcher.config; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.xml.DOMConfigurator; import org.nuxeo.common.Environment; import org.nuxeo.common.codec.Crypto; import org.nuxeo.common.codec.CryptoProperties; import org.nuxeo.common.utils.TextTemplate; import org.nuxeo.connect.update.LocalPackage; import org.nuxeo.launcher.info.ConfigurationInfo; import org.nuxeo.launcher.info.DistributionInfo; import org.nuxeo.launcher.info.InstanceInfo; import org.nuxeo.launcher.info.KeyValueInfo; import org.nuxeo.launcher.info.PackageInfo; import freemarker.template.TemplateException; /** * @author jcarsique */ public abstract class ServerConfigurator { protected static final Log log = LogFactory.getLog(ServerConfigurator.class); protected final ConfigurationGenerator generator; protected File dataDir = null; protected File logDir = null; protected File pidDir = null; protected File libDir = null; protected File tmpDir = null; protected File packagesDir = null; /** * @since 5.4.2 */ public static final List<String> NUXEO_SYSTEM_PROPERTIES = Arrays.asList(new String[] { "nuxeo.conf", "nuxeo.home", "log.id" }); protected static final String DEFAULT_CONTEXT_NAME = "/nuxeo"; private static final String NEW_FILES = ConfigurationGenerator.TEMPLATES + File.separator + "files.list"; /** * @since 5.4.2 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_LOG_DIR} instead. */ @Deprecated public static final String DEFAULT_LOG_DIR = org.nuxeo.common.Environment.DEFAULT_LOG_DIR; /** * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_DATA_DIR} instead. */ @Deprecated public static final String DEFAULT_DATA_DIR = org.nuxeo.common.Environment.DEFAULT_DATA_DIR; /** * @since 5.4.2 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_TMP_DIR} instead. */ @Deprecated public static final String DEFAULT_TMP_DIR = org.nuxeo.common.Environment.DEFAULT_TMP_DIR; public ServerConfigurator(ConfigurationGenerator configurationGenerator) { generator = configurationGenerator; } /** * @return true if server configuration files already exist */ abstract boolean isConfigured(); /** * Generate configuration files from templates and given configuration parameters * * @param config Properties with configuration parameters for template replacement * @throws ConfigurationException */ protected void parseAndCopy(Properties config) throws IOException, TemplateException, ConfigurationException { // FilenameFilter for excluding "nuxeo.defaults" files from copy final FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return !ConfigurationGenerator.NUXEO_DEFAULT_CONF.equals(name); } }; final TextTemplate templateParser = new TextTemplate(config); templateParser.setKeepEncryptedAsVar(true); templateParser.setTrim(true); templateParser.setTextParsingExtensions(config.getProperty( ConfigurationGenerator.PARAM_TEMPLATES_PARSING_EXTENSIONS, "xml,properties,nx")); templateParser.setFreemarkerParsingExtensions(config.getProperty( ConfigurationGenerator.PARAM_TEMPLATES_FREEMARKER_EXTENSIONS, "nxftl")); deleteTemplateFiles(); // add included templates directories List<String> newFilesList = new ArrayList<>(); for (File includedTemplate : generator.getIncludedTemplates()) { File[] listFiles = includedTemplate.listFiles(filter); if (listFiles != null) { String templateName = includedTemplate.getName(); log.debug(String.format("Parsing %s... %s", templateName, Arrays.toString(listFiles))); // Check for deprecation Boolean isDeprecated = Boolean.valueOf(config.getProperty(templateName + ".deprecated")); if (isDeprecated) { log.warn("WARNING: Template " + templateName + " is deprecated."); String deprecationMessage = config.getProperty(templateName + ".deprecation"); if (deprecationMessage != null) { log.warn(deprecationMessage); } } // Retrieve optional target directory if defined String outputDirectoryStr = config.getProperty(templateName + ".target"); File outputDirectory = (outputDirectoryStr != null) ? new File(generator.getNuxeoHome(), outputDirectoryStr) : getOutputDirectory(); for (File in : listFiles) { // copy template(s) directories parsing properties newFilesList.addAll(templateParser.processDirectory(in, new File(outputDirectory, in.getName()))); } } } storeNewFilesList(newFilesList); } /** * Delete files previously deployed by templates. If a file had been overwritten by a template, it will be restored. * Helps the server returning to the state before any template was applied. * * @throws IOException * @throws ConfigurationException */ private void deleteTemplateFiles() throws IOException, ConfigurationException { File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); if (!newFiles.exists()) { return; } BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(newFiles)); String line; while ((line = reader.readLine()) != null) { if (line.endsWith(".bak")) { log.debug("Restore " + line); try { File backup = new File(generator.getNuxeoHome(), line); File original = new File(generator.getNuxeoHome(), line.substring(0, line.length() - 4)); FileUtils.copyFile(backup, original); backup.delete(); } catch (IOException e) { throw new ConfigurationException(String.format("Failed to restore %s from %s\nEdit or " + "delete %s to bypass that error.", line.substring(0, line.length() - 4), line, newFiles), e); } } else { log.debug("Remove " + line); new File(generator.getNuxeoHome(), line).delete(); } } } finally { IOUtils.closeQuietly(reader); } newFiles.delete(); } /** * Store into {@link #NEW_FILES} the list of new files deployed by the templates. For later use by * {@link #deleteTemplateFiles()} * * @param newFilesList * @throws IOException */ private void storeNewFilesList(List<String> newFilesList) throws IOException { BufferedWriter writer = null; try { // Store new files listing File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); writer = new BufferedWriter(new FileWriter(newFiles, false)); int index = generator.getNuxeoHome().getCanonicalPath().length() + 1; for (String filepath : newFilesList) { writer.write(new File(filepath).getCanonicalPath().substring(index)); writer.newLine(); } } finally { IOUtils.closeQuietly(writer); } } /** * @return output directory for files generation */ protected File getOutputDirectory() { return getRuntimeHome(); } /** * @return Default data directory path relative to Nuxeo Home * @since 5.4.2 */ protected String getDefaultDataDir() { return org.nuxeo.common.Environment.DEFAULT_DATA_DIR; } /** * Returns the Home of NuxeoRuntime (same as Framework.getRuntime().getHome().getAbsolutePath()) */ protected abstract File getRuntimeHome(); /** * @return Data directory * @since 5.4.2 */ public File getDataDir() { if (dataDir == null) { dataDir = new File(generator.getNuxeoHome(), getDefaultDataDir()); } return dataDir; } /** * @return Log directory * @since 5.4.2 */ public File getLogDir() { if (logDir == null) { logDir = new File(generator.getNuxeoHome(), org.nuxeo.common.Environment.DEFAULT_LOG_DIR); } return logDir; } /** * @param dataDirStr Data directory path to set * @since 5.4.2 */ public void setDataDir(String dataDirStr) { dataDir = new File(dataDirStr); dataDir.mkdirs(); } /** * @param logDirStr Log directory path to set * @since 5.4.2 */ public void setLogDir(String logDirStr) { logDir = new File(logDirStr); logDir.mkdirs(); } /** * Initialize logs. This is called before {@link ConfigurationGenerator#init()} so the {@code logDir} field is not * yet initialized * * @since 5.4.2 */ public void initLogs() { File logFile = getLogConfFile(); try { String logDirectory = System.getProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR); if (logDirectory == null) { System.setProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR, getLogDir().getPath()); } if (logFile == null || !logFile.exists()) { System.out.println("No logs configuration, will setup a basic one."); BasicConfigurator.configure(); } else { System.out.println("Try to configure logs with " + logFile); DOMConfigurator.configure(logFile.toURI().toURL()); } log.info("Logs successfully configured."); } catch (MalformedURLException e) { log.error("Could not initialize logs with " + logFile, e); } } /** * @return Pid directory (usually known as "run directory"); Returns log directory if not set by configuration. * @since 5.4.2 */ public File getPidDir() { if (pidDir == null) { pidDir = getLogDir(); } return pidDir; } /** * @param pidDirStr Pid directory path to set * @since 5.4.2 */ public void setPidDir(String pidDirStr) { pidDir = new File(pidDirStr); pidDir.mkdirs(); } /** * Check server paths; warn if existing deprecated paths. Override this method to perform server specific checks. * * @throws ConfigurationException If deprecated paths have been detected * @since 5.4.2 */ public void checkPaths() throws ConfigurationException { File badInstanceClid = new File(generator.getNuxeoHome(), getDefaultDataDir() + File.separator + "instance.clid"); if (badInstanceClid.exists() && !getDataDir().equals(badInstanceClid.getParentFile())) { log.warn(String.format("Moving %s to %s.", badInstanceClid, getDataDir())); try { FileUtils.moveFileToDirectory(badInstanceClid, getDataDir(), true); } catch (IOException e) { throw new ConfigurationException("NXP-6722 move failed: " + e.getMessage(), e); } } File oldPackagesPath = new File(getDataDir(), getDefaultPackagesDir()); if (oldPackagesPath.exists() && !oldPackagesPath.equals(getPackagesDir())) { log.warn(String.format( "NXP-8014 Packages cache location changed. You can safely delete %s or move its content to %s", oldPackagesPath, getPackagesDir())); } } /** * @return Temporary directory * @since 5.4.2 */ public File getTmpDir() { if (tmpDir == null) { tmpDir = new File(generator.getNuxeoHome(), getDefaultTmpDir()); } return tmpDir; } /** * @return Default temporary directory path relative to Nuxeo Home * @since 5.4.2 */ public String getDefaultTmpDir() { return org.nuxeo.common.Environment.DEFAULT_TMP_DIR; } /** * @param tmpDirStr Temporary directory path to set * @since 5.4.2 */ public void setTmpDir(String tmpDirStr) { tmpDir = new File(tmpDirStr); tmpDir.mkdirs(); } /** * @see Environment * @param key directory system key * @param directory absolute or relative directory path * @since 5.4.2 */ public void setDirectory(String key, String directory) { String absoluteDirectory = setAbsolutePath(key, directory); if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { setDataDir(absoluteDirectory); } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { setLogDir(absoluteDirectory); } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { setPidDir(absoluteDirectory); } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { setTmpDir(absoluteDirectory); } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { setPackagesDir(absoluteDirectory); } else { log.error("Unknown directory key: " + key); } } /** * @param absoluteDirectory * @since 5.9.4 */ private void setPackagesDir(String packagesDirStr) { packagesDir = new File(packagesDirStr); packagesDir.mkdirs(); } /** * Make absolute the directory passed in parameter. If it was relative, then store absolute path in user config * instead of relative and return value * * @param key Directory system key * @param directory absolute or relative directory path * @return absolute directory path * @since 5.4.2 */ private String setAbsolutePath(String key, String directory) { if (!new File(directory).isAbsolute()) { directory = new File(generator.getNuxeoHome(), directory).getPath(); generator.getUserConfig().setProperty(key, directory); } return directory; } /** * @see Environment * @param key directory system key * @return Directory denoted by key * @since 5.4.2 */ public File getDirectory(String key) { if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { return getDataDir(); } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { return getLogDir(); } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { return getPidDir(); } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { return getTmpDir(); } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { return getPackagesDir(); } else { log.error("Unknown directory key: " + key); return null; } } /** * Check if oldPath exist; if so, then raise a ConfigurationException with information for fixing issue * * @param oldPath Path that must NOT exist * @param message Error message thrown with exception * @throws ConfigurationException If an old path has been discovered */ protected void checkPath(File oldPath, String message) throws ConfigurationException { if (oldPath.exists()) { log.error("Deprecated paths used."); throw new ConfigurationException(message); } } /** * @return Log4J configuration file * @since 5.4.2 */ public abstract File getLogConfFile(); /** * @return Nuxeo config directory * @since 5.4.2 */ public abstract File getConfigDir(); /** * @since 5.4.2 */ public void prepareWizardStart() { // Nothing to do by default } /** * @since 5.4.2 */ public void cleanupPostWizard() { // Nothing to do by default } /** * Override it to make the wizard available for a given server. * * @return true if configuration wizard is required before starting Nuxeo * @since 5.4.2 * @see #prepareWizardStart() * @see #cleanupPostWizard() */ public boolean isWizardAvailable() { return false; } /** * @param userConfig Properties to dump into config directory * @since 5.4.2 */ public void dumpProperties(CryptoProperties userConfig) { Properties dumpedProperties = filterSystemProperties(userConfig); File dumpedFile = generator.getDumpedConfig(); OutputStream os = null; try { os = new FileOutputStream(dumpedFile, false); dumpedProperties.store(os, "Generated by " + getClass()); } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error("Could not dump properties to " + dumpedFile, e); } finally { IOUtils.closeQuietly(os); } } /** * Extract Nuxeo properties from given Properties (System properties are removed, except those set by Nuxeo) * * @param properties Properties to be filtered * @return copy of given properties filtered out of System properties * @since 5.4.2 */ public Properties filterSystemProperties(CryptoProperties properties) { Properties dumpedProperties = new Properties(); for (@SuppressWarnings("unchecked") Enumeration<String> propertyNames = (Enumeration<String>) properties.propertyNames(); propertyNames.hasMoreElements();) { String key = propertyNames.nextElement(); // Exclude System properties except Nuxeo's System properties if (!System.getProperties().containsKey(key) || NUXEO_SYSTEM_PROPERTIES.contains(key)) { dumpedProperties.setProperty(key, properties.getRawProperty(key)); } } return dumpedProperties; } /** * @return Nuxeo's third party libraries directory * @since 5.4.1 */ public File getNuxeoLibDir() { return new File(getRuntimeHome(), "lib"); } /** * @return Server's third party libraries directory * @since 5.4.1 */ public abstract File getServerLibDir(); /** * @throws ConfigurationException * @since 5.7 */ public void verifyInstallation() throws ConfigurationException { checkPaths(); checkNetwork(); } /** * Perform server specific checks, not already done by {@link ConfigurationGenerator#checkAddressesAndPorts()} * * @throws ConfigurationException * @since 5.7 * @see ConfigurationGenerator#checkAddressesAndPorts() */ protected void checkNetwork() throws ConfigurationException { } /** * Override to add server specific parameters to the list of parameters to migrate * * @param parametersmigration * @since 5.7 */ protected void addServerSpecificParameters(Map<String, String> parametersmigration) { // Nothing to do } /** * @return Marketplace Packages directory * @since 5.9.4 */ public File getPackagesDir() { if (packagesDir == null) { packagesDir = new File(generator.getNuxeoHome(), getDefaultPackagesDir()); } return packagesDir; } /** * @return Default MP directory path relative to Nuxeo Home * @since 5.9.4 */ public String getDefaultPackagesDir() { return org.nuxeo.common.Environment.DEFAULT_MP_DIR; } /** * Introspect the server and builds the instance info * * @since 8.3 */ public InstanceInfo getInfo(String clid, List<LocalPackage> pkgs) { InstanceInfo nxInstance = new InstanceInfo(); nxInstance.NUXEO_CONF = generator.getNuxeoConf().getPath(); nxInstance.NUXEO_HOME = generator.getNuxeoHome().getPath(); // distribution File distFile = new File(generator.getConfigDir(), "distribution.properties"); if (!distFile.exists()) { // fallback in the file in templates distFile = new File(generator.getNuxeoHome(), "templates"); distFile = new File(distFile, "common"); distFile = new File(distFile, "config"); distFile = new File(distFile, "distribution.properties"); } try { nxInstance.distribution = new DistributionInfo(distFile); } catch (IOException e) { nxInstance.distribution = new DistributionInfo(); } // packages nxInstance.clid = clid; Set<String> pkgTemplates = new HashSet<>(); for (LocalPackage pkg : pkgs) { final PackageInfo info = new PackageInfo(pkg); nxInstance.packages.add(info); pkgTemplates.addAll(info.templates); } // templates nxInstance.config = new ConfigurationInfo(); nxInstance.config.dbtemplate = generator.extractDatabaseTemplateName(); String userTemplates = generator.getUserTemplates(); StringTokenizer st = new StringTokenizer(userTemplates, ","); while (st.hasMoreTokens()) { String template = st.nextToken(); if (template.equals(nxInstance.config.dbtemplate)) { continue; } if (pkgTemplates.contains(template)) { nxInstance.config.pkgtemplates.add(template); } else { File testBase = new File(generator.getNuxeoHome(), ConfigurationGenerator.TEMPLATES + File.separator + template); if (testBase.exists()) { nxInstance.config.basetemplates.add(template); } else { nxInstance.config.usertemplates.add(template); } } } // Settings from nuxeo.conf CryptoProperties userConfig = generator.getUserConfig(); for (Object item : new TreeSet<>(userConfig.keySet())) { String key = (String) item; String value = userConfig.getRawProperty(key); if (key.equals("JAVA_OPTS")) { value = generator.getJavaOpts("JAVA_OPTS", "-Xms512m -Xmx1024m"); } if (ConfigurationGenerator.SECRET_KEYS.contains(key) || key.contains("password") || key.equals(Environment.SERVER_STATUS_KEY) || Crypto.isEncrypted(value)) { value = "********"; } nxInstance.config.keyvals.add(new KeyValueInfo(key, value)); } return nxInstance; } }