001/* 002 * (C) Copyright 2010-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Julien Carsique 018 */ 019package org.nuxeo.launcher.config; 020 021import java.io.BufferedReader; 022import java.io.BufferedWriter; 023import java.io.File; 024import java.io.FileNotFoundException; 025import java.io.FileOutputStream; 026import java.io.FileReader; 027import java.io.FilenameFilter; 028import java.io.IOException; 029import java.io.OutputStreamWriter; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Enumeration; 033import java.util.HashSet; 034import java.util.List; 035import java.util.Map; 036import java.util.Properties; 037import java.util.Set; 038import java.util.StringTokenizer; 039import java.util.TreeSet; 040 041import org.apache.commons.io.FileUtils; 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044import org.apache.logging.log4j.core.config.Configurator; 045import org.apache.logging.log4j.core.config.DefaultConfiguration; 046import org.nuxeo.common.Environment; 047import org.nuxeo.common.codec.Crypto; 048import org.nuxeo.common.codec.CryptoProperties; 049import org.nuxeo.common.utils.TextTemplate; 050import org.nuxeo.connect.update.LocalPackage; 051import org.nuxeo.launcher.info.ConfigurationInfo; 052import org.nuxeo.launcher.info.DistributionInfo; 053import org.nuxeo.launcher.info.InstanceInfo; 054import org.nuxeo.launcher.info.KeyValueInfo; 055import org.nuxeo.launcher.info.PackageInfo; 056import org.nuxeo.log4j.Log4JHelper; 057 058import freemarker.template.TemplateException; 059 060/** 061 * @author jcarsique 062 */ 063public abstract class ServerConfigurator { 064 065 protected static final Log log = LogFactory.getLog(ServerConfigurator.class); 066 067 protected final ConfigurationGenerator generator; 068 069 protected File dataDir = null; 070 071 protected File logDir = null; 072 073 protected File pidDir = null; 074 075 protected File tmpDir = null; 076 077 protected File packagesDir = null; 078 079 /** 080 * @since 5.4.2 081 */ 082 public static final List<String> NUXEO_SYSTEM_PROPERTIES = Arrays.asList("nuxeo.conf", "nuxeo.home", "log.id"); 083 084 protected static final String DEFAULT_CONTEXT_NAME = "/nuxeo"; 085 086 /** @since 9.3 */ 087 public static final String JAVA_OPTS = "JAVA_OPTS"; 088 089 private static final String NEW_FILES = ConfigurationGenerator.TEMPLATES + File.separator + "files.list"; 090 091 /** 092 * @since 5.4.2 093 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_LOG_DIR} instead. 094 */ 095 @Deprecated 096 public static final String DEFAULT_LOG_DIR = org.nuxeo.common.Environment.DEFAULT_LOG_DIR; 097 098 /** 099 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_DATA_DIR} instead. 100 */ 101 @Deprecated 102 public static final String DEFAULT_DATA_DIR = org.nuxeo.common.Environment.DEFAULT_DATA_DIR; 103 104 /** 105 * @since 5.4.2 106 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_TMP_DIR} instead. 107 */ 108 @Deprecated 109 public static final String DEFAULT_TMP_DIR = org.nuxeo.common.Environment.DEFAULT_TMP_DIR; 110 111 public ServerConfigurator(ConfigurationGenerator configurationGenerator) { 112 generator = configurationGenerator; 113 } 114 115 /** 116 * @return true if server configuration files already exist 117 */ 118 abstract boolean isConfigured(); 119 120 /** 121 * Generate configuration files from templates and given configuration parameters 122 * 123 * @param config Properties with configuration parameters for template replacement 124 */ 125 protected void parseAndCopy(Properties config) throws IOException, TemplateException, ConfigurationException { 126 // FilenameFilter for excluding "nuxeo.defaults" files from copy 127 final FilenameFilter filter = (dir, name) -> !ConfigurationGenerator.NUXEO_DEFAULT_CONF.equals(name); 128 final TextTemplate templateParser = new TextTemplate(config); 129 templateParser.setKeepEncryptedAsVar(true); 130 templateParser.setTrim(true); 131 templateParser.setTextParsingExtensions( 132 config.getProperty(ConfigurationGenerator.PARAM_TEMPLATES_PARSING_EXTENSIONS, "xml,properties,nx")); 133 templateParser.setFreemarkerParsingExtensions( 134 config.getProperty(ConfigurationGenerator.PARAM_TEMPLATES_FREEMARKER_EXTENSIONS, "nxftl")); 135 136 deleteTemplateFiles(); 137 // add included templates directories 138 List<String> newFilesList = new ArrayList<>(); 139 for (File includedTemplate : generator.getIncludedTemplates()) { 140 File[] listFiles = includedTemplate.listFiles(filter); 141 if (listFiles != null) { 142 String templateName = includedTemplate.getName(); 143 log.debug(String.format("Parsing %s... %s", templateName, Arrays.toString(listFiles))); 144 // Check for deprecation 145 boolean isDeprecated = Boolean.parseBoolean(config.getProperty(templateName + ".deprecated")); 146 if (isDeprecated) { 147 log.warn("WARNING: Template " + templateName + " is deprecated."); 148 String deprecationMessage = config.getProperty(templateName + ".deprecation"); 149 if (deprecationMessage != null) { 150 log.warn(deprecationMessage); 151 } 152 } 153 // Retrieve optional target directory if defined 154 String outputDirectoryStr = config.getProperty(templateName + ".target"); 155 File outputDirectory = (outputDirectoryStr != null) 156 ? new File(generator.getNuxeoHome(), outputDirectoryStr) 157 : getOutputDirectory(); 158 for (File in : listFiles) { 159 // copy template(s) directories parsing properties 160 newFilesList.addAll(templateParser.processDirectory(in, new File(outputDirectory, in.getName()))); 161 } 162 } 163 } 164 storeNewFilesList(newFilesList); 165 } 166 167 /** 168 * Delete files previously deployed by templates. If a file had been overwritten by a template, it will be restored. 169 * Helps the server returning to the state before any template was applied. 170 */ 171 private void deleteTemplateFiles() throws IOException, ConfigurationException { 172 File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); 173 if (!newFiles.exists()) { 174 return; 175 } 176 try (BufferedReader reader = new BufferedReader(new FileReader(newFiles))) { 177 String line; 178 while ((line = reader.readLine()) != null) { 179 if (line.endsWith(".bak")) { 180 log.debug("Restore " + line); 181 try { 182 File backup = new File(generator.getNuxeoHome(), line); 183 File original = new File(generator.getNuxeoHome(), line.substring(0, line.length() - 4)); 184 FileUtils.copyFile(backup, original); 185 backup.delete(); 186 } catch (IOException e) { 187 throw new ConfigurationException(String.format( 188 "Failed to restore %s from %s\nEdit or " + "delete %s to bypass that error.", 189 line.substring(0, line.length() - 4), line, newFiles), e); 190 } 191 } else { 192 log.debug("Remove " + line); 193 new File(generator.getNuxeoHome(), line).delete(); 194 } 195 } 196 } 197 newFiles.delete(); 198 } 199 200 /** 201 * Store into {@link #NEW_FILES} the list of new files deployed by the templates. For later use by 202 * {@link #deleteTemplateFiles()} 203 */ 204 private void storeNewFilesList(List<String> newFilesList) throws IOException { 205 File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); 206 try (BufferedWriter writer = new BufferedWriter( 207 new OutputStreamWriter(new FileOutputStream(newFiles, false), "UTF-8"))) { 208 // Store new files listing 209 int index = generator.getNuxeoHome().getCanonicalPath().length() + 1; 210 for (String filepath : newFilesList) { 211 writer.write(new File(filepath).getCanonicalPath().substring(index)); 212 writer.newLine(); 213 } 214 } 215 } 216 217 /** 218 * @return output directory for files generation 219 */ 220 protected File getOutputDirectory() { 221 return getRuntimeHome(); 222 } 223 224 /** 225 * @return Default data directory path relative to Nuxeo Home 226 * @since 5.4.2 227 */ 228 protected String getDefaultDataDir() { 229 return org.nuxeo.common.Environment.DEFAULT_DATA_DIR; 230 } 231 232 /** 233 * Returns the Home of NuxeoRuntime (same as Framework.getRuntime().getHome().getAbsolutePath()) 234 */ 235 protected abstract File getRuntimeHome(); 236 237 /** 238 * @return Data directory 239 * @since 5.4.2 240 */ 241 public File getDataDir() { 242 if (dataDir == null) { 243 dataDir = new File(generator.getNuxeoHome(), getDefaultDataDir()); 244 } 245 return dataDir; 246 } 247 248 /** 249 * @return Log directory 250 * @since 5.4.2 251 */ 252 public File getLogDir() { 253 if (logDir == null) { 254 logDir = new File(generator.getNuxeoHome(), org.nuxeo.common.Environment.DEFAULT_LOG_DIR); 255 } 256 return logDir; 257 } 258 259 /** 260 * @param dataDirStr Data directory path to set 261 * @since 5.4.2 262 */ 263 public void setDataDir(String dataDirStr) { 264 dataDir = new File(dataDirStr); 265 dataDir.mkdirs(); 266 } 267 268 /** 269 * @param logDirStr Log directory path to set 270 * @since 5.4.2 271 */ 272 public void setLogDir(String logDirStr) { 273 logDir = new File(logDirStr); 274 logDir.mkdirs(); 275 } 276 277 /** 278 * Initialize logs. This is called before {@link ConfigurationGenerator#init()} so the {@code logDir} field is not 279 * yet initialized 280 * 281 * @since 5.4.2 282 */ 283 public void initLogs() { 284 File logFile = getLogConfFile(); 285 String logDirectory = System.getProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR); 286 if (logDirectory == null) { 287 System.setProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR, getLogDir().getPath()); 288 } 289 if (logFile == null || !logFile.exists()) { 290 System.out.println("No logs configuration, will setup a basic one."); 291 Configurator.initialize(new DefaultConfiguration()); 292 } else { 293 System.out.println("Try to configure logs with " + logFile); 294 Configurator.initialize(Log4JHelper.newConfiguration(logFile)); 295 } 296 log.info("Logs successfully configured."); 297 } 298 299 /** 300 * @return Pid directory (usually known as "run directory"); Returns log directory if not set by configuration. 301 * @since 5.4.2 302 */ 303 public File getPidDir() { 304 if (pidDir == null) { 305 pidDir = getLogDir(); 306 } 307 return pidDir; 308 } 309 310 /** 311 * @param pidDirStr Pid directory path to set 312 * @since 5.4.2 313 */ 314 public void setPidDir(String pidDirStr) { 315 pidDir = new File(pidDirStr); 316 pidDir.mkdirs(); 317 } 318 319 /** 320 * Check server paths; warn if existing deprecated paths. Override this method to perform server specific checks. 321 * 322 * @throws ConfigurationException If deprecated paths have been detected 323 * @since 5.4.2 324 */ 325 public void checkPaths() throws ConfigurationException { 326 File badInstanceClid = new File(generator.getNuxeoHome(), 327 getDefaultDataDir() + File.separator + "instance.clid"); 328 if (badInstanceClid.exists() && !getDataDir().equals(badInstanceClid.getParentFile())) { 329 log.warn(String.format("Moving %s to %s.", badInstanceClid, getDataDir())); 330 try { 331 FileUtils.moveFileToDirectory(badInstanceClid, getDataDir(), true); 332 } catch (IOException e) { 333 throw new ConfigurationException("NXP-6722 move failed: " + e.getMessage(), e); 334 } 335 } 336 337 File oldPackagesPath = new File(getDataDir(), getDefaultPackagesDir()); 338 if (oldPackagesPath.exists() && !oldPackagesPath.equals(getPackagesDir())) { 339 log.warn(String.format( 340 "NXP-8014 Packages cache location changed. You can safely delete %s or move its content to %s", 341 oldPackagesPath, getPackagesDir())); 342 } 343 344 } 345 346 /** 347 * @return Temporary directory 348 * @since 5.4.2 349 */ 350 public File getTmpDir() { 351 if (tmpDir == null) { 352 tmpDir = new File(generator.getNuxeoHome(), getDefaultTmpDir()); 353 } 354 return tmpDir; 355 } 356 357 /** 358 * @return Default temporary directory path relative to Nuxeo Home 359 * @since 5.4.2 360 */ 361 public String getDefaultTmpDir() { 362 return org.nuxeo.common.Environment.DEFAULT_TMP_DIR; 363 } 364 365 /** 366 * @param tmpDirStr Temporary directory path to set 367 * @since 5.4.2 368 */ 369 public void setTmpDir(String tmpDirStr) { 370 tmpDir = new File(tmpDirStr); 371 tmpDir.mkdirs(); 372 } 373 374 /** 375 * @see Environment 376 * @param key directory system key 377 * @param directory absolute or relative directory path 378 * @since 5.4.2 379 */ 380 public void setDirectory(String key, String directory) { 381 String absoluteDirectory = setAbsolutePath(key, directory); 382 if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { 383 setDataDir(absoluteDirectory); 384 } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { 385 setLogDir(absoluteDirectory); 386 } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { 387 setPidDir(absoluteDirectory); 388 } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { 389 setTmpDir(absoluteDirectory); 390 } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { 391 setPackagesDir(absoluteDirectory); 392 } else { 393 log.error("Unknown directory key: " + key); 394 } 395 } 396 397 /** 398 * @since 5.9.4 399 */ 400 private void setPackagesDir(String packagesDirStr) { 401 packagesDir = new File(packagesDirStr); 402 packagesDir.mkdirs(); 403 } 404 405 /** 406 * Make absolute the directory passed in parameter. If it was relative, then store absolute path in user config 407 * instead of relative and return value 408 * 409 * @param key Directory system key 410 * @param directory absolute or relative directory path 411 * @return absolute directory path 412 * @since 5.4.2 413 */ 414 private String setAbsolutePath(String key, String directory) { 415 if (!new File(directory).isAbsolute()) { 416 directory = new File(generator.getNuxeoHome(), directory).getPath(); 417 generator.getUserConfig().setProperty(key, directory); 418 } 419 return directory; 420 } 421 422 /** 423 * @see Environment 424 * @param key directory system key 425 * @return Directory denoted by key 426 * @since 5.4.2 427 */ 428 public File getDirectory(String key) { 429 if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { 430 return getDataDir(); 431 } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { 432 return getLogDir(); 433 } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { 434 return getPidDir(); 435 } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { 436 return getTmpDir(); 437 } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { 438 return getPackagesDir(); 439 } else { 440 log.error("Unknown directory key: " + key); 441 return null; 442 } 443 } 444 445 /** 446 * Check if oldPath exist; if so, then raise a ConfigurationException with information for fixing issue 447 * 448 * @param oldPath Path that must NOT exist 449 * @param message Error message thrown with exception 450 * @throws ConfigurationException If an old path has been discovered 451 */ 452 protected void checkPath(File oldPath, String message) throws ConfigurationException { 453 if (oldPath.exists()) { 454 log.error("Deprecated paths used."); 455 throw new ConfigurationException(message); 456 } 457 } 458 459 /** 460 * @return Log4J configuration file 461 * @since 5.4.2 462 */ 463 public abstract File getLogConfFile(); 464 465 /** 466 * @return Nuxeo config directory 467 * @since 5.4.2 468 */ 469 public abstract File getConfigDir(); 470 471 /** 472 * @since 5.4.2 473 */ 474 public void prepareWizardStart() { 475 // Nothing to do by default 476 } 477 478 /** 479 * @since 5.4.2 480 */ 481 public void cleanupPostWizard() { 482 // Nothing to do by default 483 } 484 485 /** 486 * Override it to make the wizard available for a given server. 487 * 488 * @return true if configuration wizard is required before starting Nuxeo 489 * @since 5.4.2 490 * @see #prepareWizardStart() 491 * @see #cleanupPostWizard() 492 */ 493 public boolean isWizardAvailable() { 494 return false; 495 } 496 497 /** 498 * @param userConfig Properties to dump into config directory 499 * @since 5.4.2 500 */ 501 public void dumpProperties(CryptoProperties userConfig) { 502 Properties dumpedProperties = filterSystemProperties(userConfig); 503 File dumpedFile = generator.getDumpedConfig(); 504 try (OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(dumpedFile, false), "UTF-8")) { 505 dumpedProperties.store(os, "Generated by " + getClass()); 506 } catch (FileNotFoundException e) { 507 log.error(e); 508 } catch (IOException e) { 509 log.error("Could not dump properties to " + dumpedFile, e); 510 } 511 } 512 513 /** 514 * Extract Nuxeo properties from given Properties (System properties are removed, except those set by Nuxeo) 515 * 516 * @param properties Properties to be filtered 517 * @return copy of given properties filtered out of System properties 518 * @since 5.4.2 519 */ 520 public Properties filterSystemProperties(CryptoProperties properties) { 521 Properties dumpedProperties = new Properties(); 522 for (@SuppressWarnings("unchecked") 523 Enumeration<String> propertyNames = (Enumeration<String>) properties.propertyNames(); propertyNames.hasMoreElements();) { 524 String key = propertyNames.nextElement(); 525 // Exclude System properties except Nuxeo's System properties 526 if (!System.getProperties().containsKey(key) || NUXEO_SYSTEM_PROPERTIES.contains(key)) { 527 dumpedProperties.setProperty(key, properties.getRawProperty(key)); 528 } 529 } 530 return dumpedProperties; 531 } 532 533 /** 534 * @return Nuxeo's third party libraries directory 535 * @since 5.4.1 536 */ 537 public File getNuxeoLibDir() { 538 return new File(getRuntimeHome(), "lib"); 539 } 540 541 /** 542 * @return Server's third party libraries directory 543 * @since 5.4.1 544 */ 545 public abstract File getServerLibDir(); 546 547 /** 548 * @since 5.7 549 */ 550 public void verifyInstallation() throws ConfigurationException { 551 checkPaths(); 552 checkNetwork(); 553 } 554 555 /** 556 * Perform server specific checks, not already done by {@link ConfigurationGenerator#checkAddressesAndPorts()} 557 * 558 * @since 5.7 559 * @see ConfigurationGenerator#checkAddressesAndPorts() 560 */ 561 protected void checkNetwork() throws ConfigurationException { 562 } 563 564 /** 565 * Override to add server specific parameters to the list of parameters to migrate 566 * 567 * @since 5.7 568 */ 569 protected void addServerSpecificParameters(Map<String, String> parametersMigration) { 570 // Nothing to do 571 } 572 573 /** 574 * @return Marketplace Packages directory 575 * @since 5.9.4 576 */ 577 public File getPackagesDir() { 578 if (packagesDir == null) { 579 packagesDir = new File(generator.getNuxeoHome(), getDefaultPackagesDir()); 580 } 581 return packagesDir; 582 } 583 584 /** 585 * @return Default MP directory path relative to Nuxeo Home 586 * @since 5.9.4 587 */ 588 public String getDefaultPackagesDir() { 589 return org.nuxeo.common.Environment.DEFAULT_MP_DIR; 590 } 591 592 /** 593 * Introspect the server and builds the instance info 594 * 595 * @since 8.3 596 */ 597 public InstanceInfo getInfo(String clid, List<LocalPackage> pkgs) { 598 InstanceInfo nxInstance = new InstanceInfo(); 599 nxInstance.NUXEO_CONF = generator.getNuxeoConf().getPath(); 600 nxInstance.NUXEO_HOME = generator.getNuxeoHome().getPath(); 601 // distribution 602 File distFile = new File(generator.getConfigDir(), "distribution.properties"); 603 if (!distFile.exists()) { 604 // fallback in the file in templates 605 distFile = new File(generator.getNuxeoHome(), "templates"); 606 distFile = new File(distFile, "common"); 607 distFile = new File(distFile, "config"); 608 distFile = new File(distFile, "distribution.properties"); 609 } 610 try { 611 nxInstance.distribution = new DistributionInfo(distFile); 612 } catch (IOException e) { 613 nxInstance.distribution = new DistributionInfo(); 614 } 615 // packages 616 nxInstance.clid = clid; 617 Set<String> pkgTemplates = new HashSet<>(); 618 for (LocalPackage pkg : pkgs) { 619 final PackageInfo info = new PackageInfo(pkg); 620 nxInstance.packages.add(info); 621 pkgTemplates.addAll(info.templates); 622 } 623 // templates 624 nxInstance.config = new ConfigurationInfo(); 625 nxInstance.config.dbtemplate = generator.extractDatabaseTemplateName(); 626 String userTemplates = generator.getUserTemplates(); 627 StringTokenizer st = new StringTokenizer(userTemplates, ","); 628 while (st.hasMoreTokens()) { 629 String template = st.nextToken(); 630 if (template.equals(nxInstance.config.dbtemplate)) { 631 continue; 632 } 633 if (pkgTemplates.contains(template)) { 634 nxInstance.config.pkgtemplates.add(template); 635 } else { 636 File testBase = new File(generator.getNuxeoHome(), 637 ConfigurationGenerator.TEMPLATES + File.separator + template); 638 if (testBase.exists()) { 639 nxInstance.config.basetemplates.add(template); 640 } else { 641 nxInstance.config.usertemplates.add(template); 642 } 643 } 644 } 645 // Settings from nuxeo.conf 646 CryptoProperties userConfig = generator.getUserConfig(); 647 for (Object item : new TreeSet<>(userConfig.keySet())) { 648 String key = (String) item; 649 String value = userConfig.getRawProperty(key); 650 if (JAVA_OPTS.equals(key)) { 651 value = generator.getJavaOptsString(); 652 } 653 if (ConfigurationGenerator.SECRET_KEYS.contains(key) || key.contains("password") 654 || key.equals(Environment.SERVER_STATUS_KEY) || Crypto.isEncrypted(value)) { 655 value = "********"; 656 } 657 nxInstance.config.keyvals.add(new KeyValueInfo(key, value)); 658 } 659 return nxInstance; 660 } 661}