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