001/* 002 * (C) Copyright 2010-2020 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 * Kevin Leturc <kleturc@nuxeo.com> 019 * Frantz Fischer <ffischer@nuxeo.com> 020 */ 021package org.nuxeo.launcher.config; 022 023import static java.nio.charset.StandardCharsets.ISO_8859_1; 024import static java.nio.charset.StandardCharsets.US_ASCII; 025import static java.nio.charset.StandardCharsets.UTF_8; 026import static java.util.Arrays.asList; 027import static java.util.Collections.singletonList; 028import static org.nuxeo.launcher.config.ServerConfigurator.PARAM_HTTP_TOMCAT_ADMIN_PORT; 029 030import java.io.BufferedInputStream; 031import java.io.BufferedReader; 032import java.io.BufferedWriter; 033import java.io.File; 034import java.io.FileInputStream; 035import java.io.FileNotFoundException; 036import java.io.FileReader; 037import java.io.FileWriter; 038import java.io.IOException; 039import java.io.InputStreamReader; 040import java.io.StringWriter; 041import java.io.Writer; 042import java.net.Inet6Address; 043import java.net.InetAddress; 044import java.net.MalformedURLException; 045import java.net.ServerSocket; 046import java.net.URL; 047import java.net.URLClassLoader; 048import java.net.UnknownHostException; 049import java.nio.ByteBuffer; 050import java.nio.charset.CharacterCodingException; 051import java.nio.charset.Charset; 052import java.nio.charset.CharsetDecoder; 053import java.security.MessageDigest; 054import java.sql.Connection; 055import java.sql.Driver; 056import java.sql.DriverManager; 057import java.sql.SQLException; 058import java.util.ArrayList; 059import java.util.Arrays; 060import java.util.Enumeration; 061import java.util.HashMap; 062import java.util.HashSet; 063import java.util.Hashtable; 064import java.util.List; 065import java.util.Map; 066import java.util.Map.Entry; 067import java.util.Objects; 068import java.util.Properties; 069import java.util.Set; 070import java.util.StringTokenizer; 071import java.util.TreeSet; 072import java.util.UUID; 073import java.util.function.Function; 074import java.util.regex.Matcher; 075import java.util.regex.Pattern; 076import java.util.stream.Collectors; 077import java.util.stream.Stream; 078 079import javax.naming.NamingException; 080import javax.naming.directory.DirContext; 081import javax.naming.directory.InitialDirContext; 082 083import org.apache.commons.codec.binary.Hex; 084import org.apache.commons.codec.digest.DigestUtils; 085import org.apache.commons.lang3.ArrayUtils; 086import org.apache.commons.lang3.StringUtils; 087import org.apache.commons.lang3.SystemUtils; 088import org.apache.commons.text.StringSubstitutor; 089import org.apache.logging.log4j.Level; 090import org.apache.logging.log4j.LogManager; 091import org.apache.logging.log4j.Logger; 092import org.apache.logging.log4j.core.LoggerContext; 093import org.nuxeo.common.Environment; 094import org.nuxeo.common.codec.Crypto; 095import org.nuxeo.common.codec.CryptoProperties; 096import org.nuxeo.common.utils.TextTemplate; 097import org.nuxeo.launcher.commons.DatabaseDriverException; 098import org.nuxeo.launcher.config.JVMVersion.UpTo; 099import org.nuxeo.log4j.Log4JHelper; 100 101import freemarker.core.ParseException; 102import freemarker.template.TemplateException; 103 104/** 105 * Builder for server configuration and datasource files from templates and properties. 106 * 107 * @author jcarsique 108 */ 109public class ConfigurationGenerator { 110 111 private static final Logger log = LogManager.getLogger(ConfigurationGenerator.class); 112 113 /** @since 11.1 */ 114 public static final String NUXEO_ENVIRONMENT = "NUXEO_ENVIRONMENT"; 115 116 /** @since 11.1 */ 117 public static final String NUXEO_PROFILES = "NUXEO_PROFILES"; 118 119 /** 120 * @since 6.0 121 * @implNote also used for profiles 122 */ 123 public static final String TEMPLATE_SEPARATOR = ","; 124 125 /** 126 * Accurate but not used internally. NXP-18023: Java 8 update 40+ required 127 * 128 * @since 5.7 129 */ 130 public static final String[] COMPLIANT_JAVA_VERSIONS = new String[] { "1.8.0_40", "11" }; 131 132 /** @since 5.6 */ 133 protected static final String CONFIGURATION_PROPERTIES = "configuration.properties"; 134 135 public static final String NUXEO_CONF = "nuxeo.conf"; 136 137 public static final String TEMPLATES = "templates"; 138 139 public static final String NUXEO_DEFAULT_CONF = "nuxeo.defaults"; 140 141 /** @since 11.1 */ 142 public static final String NUXEO_ENVIRONMENT_CONF_FORMAT = "nuxeo.%s"; 143 144 /** 145 * Absolute or relative PATH to the user chosen templates (comma separated list) 146 */ 147 public static final String PARAM_TEMPLATES_NAME = "nuxeo.templates"; 148 149 public static final String PARAM_TEMPLATE_DBNAME = "nuxeo.dbtemplate"; 150 151 /** @since 9.3 */ 152 public static final String PARAM_TEMPLATE_DBSECONDARY_NAME = "nuxeo.dbnosqltemplate"; 153 154 public static final String PARAM_TEMPLATE_DBTYPE = "nuxeo.db.type"; 155 156 /** @since 9.3 */ 157 public static final String PARAM_TEMPLATE_DBSECONDARY_TYPE = "nuxeo.dbsecondary.type"; 158 159 public static final String OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.templates.parsing.extensions"; 160 161 public static final String PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.plaintext_parsing_extensions"; 162 163 public static final String PARAM_TEMPLATES_FREEMARKER_EXTENSIONS = "nuxeo.freemarker_parsing_extensions"; 164 165 /** 166 * Absolute or relative PATH to the included templates (comma separated list) 167 */ 168 protected static final String PARAM_INCLUDED_TEMPLATES = "nuxeo.template.includes"; 169 170 public static final String PARAM_FORCE_GENERATION = "nuxeo.force.generation"; 171 172 public static final String BOUNDARY_BEGIN = "### BEGIN - DO NOT EDIT BETWEEN BEGIN AND END ###"; 173 174 public static final String BOUNDARY_END = "### END - DO NOT EDIT BETWEEN BEGIN AND END ###"; 175 176 public static final List<String> DB_LIST = asList("default", "mongodb", "postgresql", "oracle", "mysql", "mariadb", 177 "mssql", "db2"); 178 179 public static final List<String> DB_SECONDARY_LIST = singletonList("none"); 180 181 public static final List<String> DB_EXCLUDE_CHECK_LIST = asList("default", "none", "mongodb"); 182 183 /** 184 * @deprecated since 11.1, Nuxeo Wizard has been removed. 185 */ 186 @Deprecated(since = "11.1") 187 public static final String PARAM_WIZARD_DONE = "nuxeo.wizard.done"; 188 189 /** 190 * @deprecated since 11.1, Nuxeo Wizard has been removed. 191 */ 192 @Deprecated(since = "11.1") 193 public static final String PARAM_WIZARD_RESTART_PARAMS = "wizard.restart.params"; 194 195 public static final String PARAM_FAKE_WINDOWS = "org.nuxeo.fake.vindoz"; 196 197 public static final String PARAM_LOOPBACK_URL = "nuxeo.loopback.url"; 198 199 public static final int MIN_PORT = 1; 200 201 public static final int MAX_PORT = 65535; 202 203 public static final int ADDRESS_PING_TIMEOUT = 1000; 204 205 public static final String PARAM_BIND_ADDRESS = "nuxeo.bind.address"; 206 207 public static final String PARAM_HTTP_PORT = "nuxeo.server.http.port"; 208 209 /** 210 * @deprecated Since 7.4. Use {@link Environment#SERVER_STATUS_KEY} instead 211 */ 212 @Deprecated 213 public static final String PARAM_STATUS_KEY = Environment.SERVER_STATUS_KEY; 214 215 public static final String PARAM_CONTEXT_PATH = "org.nuxeo.ecm.contextPath"; 216 217 /** 218 * @deprecated since 11.1, Nuxeo Wizard has been removed. 219 */ 220 @Deprecated(since = "11.1") 221 public static final String PARAM_MP_DIR = "nuxeo.distribution.marketplace.dir"; 222 223 /** 224 * @deprecated since 11.1, Nuxeo Wizard has been removed. 225 */ 226 @Deprecated(since = "11.1") 227 public static final String DISTRIBUTION_MP_DIR = "setupWizardDownloads"; 228 229 public static final String INSTALL_AFTER_RESTART = "installAfterRestart.log"; 230 231 public static final String PARAM_DB_DRIVER = "nuxeo.db.driver"; 232 233 public static final String PARAM_DB_JDBC_URL = "nuxeo.db.jdbc.url"; 234 235 public static final String PARAM_DB_HOST = "nuxeo.db.host"; 236 237 public static final String PARAM_DB_PORT = "nuxeo.db.port"; 238 239 public static final String PARAM_DB_NAME = "nuxeo.db.name"; 240 241 public static final String PARAM_DB_USER = "nuxeo.db.user"; 242 243 public static final String PARAM_DB_PWD = "nuxeo.db.password"; 244 245 /** 246 * @since 8.1 247 * @deprecated since 11.1, seems unused 248 */ 249 @Deprecated(since = "11.1") 250 public static final String PARAM_MONGODB_NAME = "nuxeo.mongodb.dbname"; 251 252 /** 253 * @since 8.1 254 * @deprecated since 11.1, seems unused 255 */ 256 @Deprecated(since = "11.1") 257 public static final String PARAM_MONGODB_SERVER = "nuxeo.mongodb.server"; 258 259 /** 260 * Catch values like ${env:PARAM_KEY:defaultValue} 261 * 262 * @since 9.1 263 */ 264 private static final Pattern ENV_VALUE_PATTERN = Pattern.compile( 265 "\\$\\{env(?<boolean>\\?\\?)?:(?<envparam>\\w*)(:?(?<defaultvalue>.*?)?)?\\}"); 266 267 /** 268 * Java options split by spaces followed by an even number of quotes (or zero). 269 * 270 * @since 9.3 271 */ 272 protected static final Pattern JAVA_OPTS_PATTERN = Pattern.compile("[ ]+(?=([^\"]*\"[^\"]*\")*[^\"]*$)"); 273 274 /** 275 * Keys which value must be displayed thoughtfully 276 * 277 * @since 8.1 278 */ 279 public static final List<String> SECRET_KEYS = asList(PARAM_DB_PWD, "mailservice.password", 280 "mail.transport.password", "nuxeo.http.proxy.password", "nuxeo.ldap.bindpassword", 281 "nuxeo.user.emergency.password"); 282 283 /** 284 * @deprecated Since 7.10. Use {@link Environment#PRODUCT_NAME} 285 */ 286 @Deprecated 287 public static final String PARAM_PRODUCT_NAME = Environment.PRODUCT_NAME; 288 289 /** 290 * @deprecated Since 7.10. Use {@link Environment#PRODUCT_VERSION} 291 */ 292 @Deprecated 293 public static final String PARAM_PRODUCT_VERSION = Environment.PRODUCT_VERSION; 294 295 /** @since 5.6 */ 296 public static final String PARAM_NUXEO_URL = "nuxeo.url"; 297 298 /** 299 * Global dev property, duplicated from runtime framework 300 * 301 * @since 5.6 302 */ 303 public static final String NUXEO_DEV_SYSTEM_PROP = "org.nuxeo.dev"; 304 305 /** 306 * Seam hot reload property, also controlled by {@link #NUXEO_DEV_SYSTEM_PROP} 307 * 308 * @since 5.6 309 */ 310 public static final String SEAM_DEBUG_SYSTEM_PROP = "org.nuxeo.seam.debug"; 311 312 /** @since 8.4 */ 313 public static final String JVMCHECK_PROP = "jvmcheck"; 314 315 /** @since 8.4 */ 316 public static final String JVMCHECK_FAIL = "fail"; 317 318 /** @since 8.4 */ 319 public static final String JVMCHECK_NOFAIL = "nofail"; 320 321 /** 322 * Java options configured in {@code bin/nuxeo.conf} and {@code bin/nuxeoctl}. 323 * 324 * @since 9.3 325 */ 326 public static final String JAVA_OPTS_PROP = "launcher.java.opts"; 327 328 public static final String VERSIONED_REGEX = "(-\\d+(\\.\\d+)*)?"; 329 330 public static final String BOOTSTRAP_JAR_REGEX = "bootstrap" + VERSIONED_REGEX + ".jar"; 331 332 public static final String JULI_JAR_REGEX = "tomcat-juli" + VERSIONED_REGEX + ".jar"; 333 334 private final File nuxeoHome; 335 336 private final File nuxeoBinDir; 337 338 // User configuration file 339 private final File nuxeoConf; 340 341 // nuxeo templates directory 342 private final File nuxeoTemplates; 343 344 // Chosen templates 345 private final List<File> includedTemplates = new ArrayList<>(); 346 347 private final ServerConfigurator serverConfigurator; 348 349 private final BackingServiceConfigurator backingServicesConfigurator; 350 351 private boolean forceGeneration; 352 353 private Properties defaultConfig; 354 355 private CryptoProperties userConfig; 356 357 private boolean configurable = false; 358 359 private boolean onceGeneration = false; 360 361 private String templates; 362 363 // if PARAM_FORCE_GENERATION=once, set to false; else keep current value 364 private boolean setOnceToFalse = true; 365 366 // if PARAM_FORCE_GENERATION=false, set to once; else keep the current value 367 private boolean setFalseToOnce = false; 368 369 private final Level logLevel; 370 371 private static boolean hideDeprecationWarnings = false; 372 373 private Environment env; 374 375 private Properties storedConfig; 376 377 private String currentConfigurationDigest; 378 379 protected static final Map<String, String> parametersMigration = Map.ofEntries( 380 Map.entry(OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS, PARAM_TEMPLATES_PARSING_EXTENSIONS), // 381 Map.entry("nuxeo.db.user.separator.key", "nuxeo.db.user_separator_key"), // 382 Map.entry("mail.pop3.host", "mail.store.host"), // 383 Map.entry("mail.pop3.port", "mail.store.port"), // 384 Map.entry("mail.smtp.host", "mail.transport.host"), // 385 Map.entry("mail.smtp.port", "mail.transport.port"), // 386 Map.entry("mail.smtp.username", "mail.transport.username"), // 387 Map.entry("mail.transport.username", "mail.transport.user"), // 388 Map.entry("mail.smtp.password", "mail.transport.password"), // 389 Map.entry("mail.smtp.usetls", "mail.transport.usetls"), // 390 Map.entry("mail.smtp.auth", "mail.transport.auth"), // 391 Map.entry("nuxeo.server.tomcat-admin.port", PARAM_HTTP_TOMCAT_ADMIN_PORT)); 392 393 public ConfigurationGenerator() { 394 this(true, false); 395 } 396 397 /** 398 * @param quiet Suppress info level messages from the console output 399 * @param debug Activate debug level logging 400 * @since 5.6 401 */ 402 public ConfigurationGenerator(boolean quiet, boolean debug) { 403 logLevel = quiet ? Level.DEBUG : Level.INFO; 404 File serverHome = Environment.getDefault().getServerHome(); 405 if (serverHome != null) { 406 nuxeoHome = serverHome.getAbsoluteFile(); 407 } else { 408 File userDir = new File(System.getProperty("user.dir")); 409 if ("bin".equalsIgnoreCase(userDir.getName())) { 410 nuxeoHome = userDir.getParentFile().getAbsoluteFile(); 411 } else { 412 nuxeoHome = userDir.getAbsoluteFile(); 413 } 414 } 415 nuxeoBinDir = new File(nuxeoHome, "bin"); 416 String nuxeoConfPath = System.getProperty(NUXEO_CONF); 417 if (nuxeoConfPath != null) { 418 nuxeoConf = new File(nuxeoConfPath).getAbsoluteFile(); 419 } else { 420 nuxeoConf = new File(nuxeoHome, "bin" + File.separator + "nuxeo.conf").getAbsoluteFile(); 421 } 422 System.setProperty(NUXEO_CONF, nuxeoConf.getPath()); 423 424 nuxeoTemplates = new File(nuxeoHome, TEMPLATES); 425 serverConfigurator = new ServerConfigurator(this); 426 if (LoggerContext.getContext(false).getRootLogger().getAppenders().isEmpty()) { 427 serverConfigurator.initLogs(); 428 } 429 backingServicesConfigurator = new BackingServiceConfigurator(this); 430 log.log(logLevel, "Nuxeo home: {}", nuxeoHome::getPath); 431 log.log(logLevel, "Nuxeo configuration: {}", nuxeoConf::getPath); 432 String nuxeoProfiles = getEnvironment(NUXEO_PROFILES); 433 if (StringUtils.isNotBlank(nuxeoProfiles)) { 434 log.log(logLevel, "Nuxeo profiles: {}", nuxeoProfiles); 435 } 436 } 437 438 public boolean isConfigurable() { 439 return configurable; 440 } 441 442 /** 443 * @since 5.7 444 */ 445 protected Properties getStoredConfig() { 446 if (storedConfig == null) { 447 updateStoredConfig(); 448 } 449 return storedConfig; 450 } 451 452 public void hideDeprecationWarnings(boolean hide) { 453 hideDeprecationWarnings = hide; 454 } 455 456 /** 457 * @see #PARAM_FORCE_GENERATION 458 */ 459 public void setForceGeneration(boolean forceGeneration) { 460 this.forceGeneration = forceGeneration; 461 } 462 463 /** 464 * @see #PARAM_FORCE_GENERATION 465 * @return true if configuration will be generated from templates 466 * @since 5.4.2 467 */ 468 public boolean isForceGeneration() { 469 return forceGeneration; 470 } 471 472 public CryptoProperties getUserConfig() { 473 return userConfig; 474 } 475 476 /** 477 * @since 5.4.2 478 */ 479 public final ServerConfigurator getServerConfigurator() { 480 return serverConfigurator; 481 } 482 483 /** 484 * Runs the configuration files generation. 485 */ 486 public void run() throws ConfigurationException { 487 if (init()) { 488 if (!serverConfigurator.isConfigured()) { 489 log.info("No current configuration, generating files..."); 490 generateFiles(); 491 } else if (forceGeneration) { 492 log.info("Configuration files generation (nuxeo.force.generation={})...", 493 () -> userConfig.getProperty(PARAM_FORCE_GENERATION)); 494 generateFiles(); 495 } else { 496 log.info( 497 "Server already configured (set nuxeo.force.generation=true to force configuration files generation)."); 498 } 499 } 500 } 501 502 /** 503 * Initialize configurator, check requirements and load current configuration 504 * 505 * @return returns true if current install is configurable, else returns false 506 */ 507 public boolean init() { 508 return init(false); 509 } 510 511 /** 512 * Initialize configurator, check requirements and load current configuration 513 * 514 * @since 5.6 515 * @param forceReload If true, forces configuration reload. 516 * @return returns true if current install is configurable, else returns false 517 */ 518 public boolean init(boolean forceReload) { 519 if (!nuxeoConf.exists()) { 520 log.info("Missing {}", nuxeoConf); 521 configurable = false; 522 userConfig = new CryptoProperties(); 523 defaultConfig = new Properties(); 524 } else if (userConfig == null || userConfig.size() == 0 || forceReload) { 525 try { 526 if (forceReload) { 527 // force 'templates' reload 528 templates = null; 529 } 530 setBasicConfiguration(); 531 configurable = true; 532 } catch (ConfigurationException e) { 533 log.warn("Error reading basic configuration.", e); 534 configurable = false; 535 } 536 } else { 537 configurable = true; 538 } 539 return configurable; 540 } 541 542 /** 543 * @return Old templates 544 */ 545 public String changeTemplates(String newTemplates) { 546 String oldTemplates = templates; 547 templates = newTemplates; 548 try { 549 setBasicConfiguration(false); 550 configurable = true; 551 } catch (ConfigurationException e) { 552 log.warn("Error reading basic configuration.", e); 553 configurable = false; 554 } 555 return oldTemplates; 556 } 557 558 /** 559 * Change templates using given database template 560 * 561 * @param dbTemplate new database template 562 * @since 5.4.2 563 */ 564 public void changeDBTemplate(String dbTemplate) { 565 changeTemplates(rebuildTemplatesStr(dbTemplate)); 566 } 567 568 private void setBasicConfiguration() throws ConfigurationException { 569 setBasicConfiguration(true); 570 } 571 572 private void setBasicConfiguration(boolean save) throws ConfigurationException { 573 if (isInvalidNuxeoDefaults(nuxeoTemplates)) { 574 throw new ConfigurationException("Missing nuxeo.defaults configuration in: " + nuxeoTemplates); 575 } 576 try { 577 // Load default configuration 578 defaultConfig = loadNuxeoDefaults(nuxeoTemplates); 579 // Add System properties 580 defaultConfig.putAll(System.getProperties()); 581 userConfig = new CryptoProperties(defaultConfig); 582 583 // If Windows, replace backslashes in paths in nuxeo.conf 584 if (SystemUtils.IS_OS_WINDOWS) { 585 replaceBackslashes(); 586 } 587 // Load user configuration 588 userConfig.putAll(loadTrimmedProperties(nuxeoConf)); 589 onceGeneration = "once".equals(userConfig.getProperty(PARAM_FORCE_GENERATION)); 590 forceGeneration = onceGeneration 591 || Boolean.parseBoolean(userConfig.getProperty(PARAM_FORCE_GENERATION, "false")); 592 checkForDeprecatedParameters(userConfig); 593 594 // Synchronize directories between serverConfigurator and 595 // userConfig/defaultConfig 596 setDirectoryWithProperty(Environment.NUXEO_DATA_DIR); 597 setDirectoryWithProperty(Environment.NUXEO_LOG_DIR); 598 setDirectoryWithProperty(Environment.NUXEO_PID_DIR); 599 setDirectoryWithProperty(Environment.NUXEO_TMP_DIR); 600 setDirectoryWithProperty(Environment.NUXEO_MP_DIR); 601 } catch (NullPointerException e) { 602 throw new ConfigurationException("Missing file", e); 603 } catch (FileNotFoundException e) { 604 throw new ConfigurationException("Missing file: " + nuxeoConf, e); 605 } catch (IOException e) { 606 throw new ConfigurationException("Error reading " + nuxeoConf, e); 607 } 608 609 // Override default configuration with specific configuration(s) of 610 // the chosen template(s) which can be outside of server filesystem 611 try { 612 includeTemplates(); 613 checkForDeprecatedParameters(defaultConfig); 614 extractDatabaseTemplateName(); 615 extractSecondaryDatabaseTemplateName(); 616 } catch (FileNotFoundException e) { 617 throw new ConfigurationException("Missing file", e); 618 } catch (IOException e) { 619 throw new ConfigurationException("Error reading " + nuxeoConf, e); 620 } 621 622 Map<String, String> newParametersToSave = evalDynamicProperties(); 623 if (save && newParametersToSave != null && !newParametersToSave.isEmpty()) { 624 saveConfiguration(newParametersToSave, false, false); 625 } 626 627 logDebugInformation(); 628 } 629 630 /** 631 * @since 5.7 632 */ 633 protected void includeTemplates() throws IOException { 634 includedTemplates.clear(); 635 String templates = getUserTemplates(); 636 String profiles = getEnvironment(NUXEO_PROFILES); 637 if (StringUtils.isNotBlank(profiles)) { 638 templates += TEMPLATE_SEPARATOR + profiles; 639 } 640 List<File> orderedTemplates = includeTemplates(templates); 641 includedTemplates.clear(); 642 includedTemplates.addAll(orderedTemplates); 643 log.debug(includedTemplates); 644 } 645 646 private void logDebugInformation() { 647 String devPropValue = userConfig.getProperty(NUXEO_DEV_SYSTEM_PROP); 648 if (Boolean.parseBoolean(devPropValue)) { 649 log.debug("Nuxeo Dev mode enabled"); 650 } else { 651 log.debug("Nuxeo Dev mode is not enabled"); 652 } 653 654 // XXX: cannot init seam debug mode when global debug mode is set, as 655 // it needs to be activated at startup, and requires the seam-debug jar 656 // to be in the classpath anyway 657 String seamDebugPropValue = userConfig.getProperty(SEAM_DEBUG_SYSTEM_PROP); 658 if (Boolean.parseBoolean(seamDebugPropValue)) { 659 log.debug("Nuxeo Seam HotReload is enabled"); 660 } else { 661 log.debug("Nuxeo Seam HotReload is not enabled"); 662 } 663 } 664 665 /** 666 * Generate properties which values are based on others 667 * 668 * @return Map with new parameters to save in {@code nuxeoConf} 669 * @since 5.5 670 */ 671 protected Map<String, String> evalDynamicProperties() throws ConfigurationException { 672 Map<String, String> newParametersToSave = new HashMap<>(); 673 evalEnvironmentVariables(newParametersToSave); 674 evalLoopbackURL(); 675 evalServerStatusKey(newParametersToSave); 676 return newParametersToSave; 677 } 678 679 /** 680 * Expand environment variable for properties values of the form ${env:MY_VAR}. 681 * 682 * @since 9.1 683 */ 684 protected void evalEnvironmentVariables(Map<String, String> newParametersToSave) { 685 for (Object keyObject : userConfig.keySet()) { 686 String key = (String) keyObject; 687 String value = userConfig.getProperty(key); 688 689 if (StringUtils.isNotBlank(value)) { 690 String newValue = replaceEnvironmentVariables(value); 691 if (!value.equals(newValue)) { 692 newParametersToSave.put(key, newValue); 693 } 694 } 695 } 696 } 697 698 private String replaceEnvironmentVariables(String value) { 699 if (StringUtils.isBlank(value)) { 700 return value; 701 } 702 703 Matcher matcher = ENV_VALUE_PATTERN.matcher(value); 704 StringBuffer sb = new StringBuffer(); 705 while (matcher.find()) { 706 boolean booleanValue = "??".equals(matcher.group("boolean")); 707 String envVarName = matcher.group("envparam"); 708 String defaultValue = matcher.group("defaultvalue"); 709 710 String envValue = getEnvironment(envVarName); 711 712 String result; 713 if (booleanValue) { 714 result = StringUtils.isBlank(envValue) ? "false" : "true"; 715 } else { 716 result = StringUtils.isBlank(envValue) ? defaultValue : envValue; 717 } 718 matcher.appendReplacement(sb, result); 719 } 720 matcher.appendTail(sb); 721 722 return sb.toString(); 723 724 } 725 726 /** 727 * Generate a server status key if not already set 728 * 729 * @see Environment#SERVER_STATUS_KEY 730 * @since 5.5 731 */ 732 private void evalServerStatusKey(Map<String, String> newParametersToSave) { 733 if (userConfig.getProperty(Environment.SERVER_STATUS_KEY) == null) { 734 newParametersToSave.put(Environment.SERVER_STATUS_KEY, UUID.randomUUID().toString().substring(0, 8)); 735 } 736 } 737 738 private void evalLoopbackURL() throws ConfigurationException { 739 String loopbackURL = userConfig.getProperty(PARAM_LOOPBACK_URL); 740 if (loopbackURL != null) { 741 log.debug("Using configured loop back url: {}", loopbackURL); 742 return; 743 } 744 InetAddress bindAddress = getBindAddress(); 745 String httpPort = userConfig.getProperty(PARAM_HTTP_PORT); 746 String contextPath = userConfig.getProperty(PARAM_CONTEXT_PATH); 747 // Is IPv6 or IPv4 ? 748 if (bindAddress instanceof Inet6Address) { 749 loopbackURL = "http://[" + bindAddress.getHostAddress() + "]:" + httpPort + contextPath; 750 } else { 751 loopbackURL = "http://" + bindAddress.getHostAddress() + ":" + httpPort + contextPath; 752 } 753 log.debug("Set as loop back URL: {}", loopbackURL); 754 defaultConfig.setProperty(PARAM_LOOPBACK_URL, loopbackURL); 755 } 756 757 /** 758 * Read nuxeo.conf, replace backslashes in paths and write new nuxeo.conf 759 * 760 * @throws ConfigurationException if any error reading or writing nuxeo.conf 761 * @since 5.4.1 762 */ 763 protected void replaceBackslashes() throws ConfigurationException { 764 StringBuilder sb = new StringBuilder(); 765 try (BufferedReader reader = new BufferedReader(new FileReader(nuxeoConf))) { 766 String line; 767 while ((line = reader.readLine()) != null) { 768 if (line.matches(".*:\\\\.*")) { 769 line = line.replaceAll("\\\\", "/"); 770 } 771 sb.append(line).append(System.lineSeparator()); 772 } 773 } catch (IOException e) { 774 throw new ConfigurationException("Error reading " + nuxeoConf, e); 775 } 776 try (FileWriter writer = new FileWriter(nuxeoConf, false)) { 777 // Copy back file content 778 writer.append(sb.toString()); 779 } catch (IOException e) { 780 throw new ConfigurationException("Error writing in " + nuxeoConf, e); 781 } 782 } 783 784 /** 785 * @since 5.4.2 786 * @param key Directory system key 787 * @see Environment 788 */ 789 public void setDirectoryWithProperty(String key) { 790 String directory = userConfig.getProperty(key); 791 if (directory == null) { 792 defaultConfig.setProperty(key, serverConfigurator.getDirectory(key).getPath()); 793 } else { 794 serverConfigurator.setDirectory(key, directory); 795 } 796 } 797 798 public String getUserTemplates() { 799 if (templates == null) { 800 templates = userConfig.getProperty(PARAM_TEMPLATES_NAME); 801 } 802 if (templates == null) { 803 log.warn("No template found in configuration! Fallback on 'default'."); 804 templates = "default"; 805 } 806 templates = replaceEnvironmentVariables(templates); 807 userConfig.setProperty(PARAM_TEMPLATES_NAME, templates); 808 return templates; 809 } 810 811 protected void generateFiles() throws ConfigurationException { 812 try { 813 serverConfigurator.parseAndCopy(userConfig); 814 serverConfigurator.dumpProperties(userConfig); 815 log.info("Configuration files generated."); 816 // keep true or false, switch once to false 817 if (onceGeneration) { 818 setOnceToFalse = true; 819 writeConfiguration(); 820 } 821 } catch (FileNotFoundException e) { 822 throw new ConfigurationException("Missing file: " + e.getMessage(), e); 823 } catch (TemplateException | ParseException e) { 824 throw new ConfigurationException("Could not process FreeMarker template: " + e.getMessage(), e); 825 } catch (IOException e) { 826 throw new ConfigurationException("Configuration failure: " + e.getMessage(), e); 827 } 828 } 829 830 private List<File> includeTemplates(String templatesList) throws IOException { 831 List<File> orderedTemplates = new ArrayList<>(); 832 StringTokenizer st = new StringTokenizer(templatesList, TEMPLATE_SEPARATOR); 833 while (st.hasMoreTokens()) { 834 String nextToken = replaceEnvironmentVariables(st.nextToken()); 835 File chosenTemplate = new File(nextToken); 836 // is it absolute and existing or relative path ? 837 if (!chosenTemplate.exists() || !chosenTemplate.getPath().equals(chosenTemplate.getAbsolutePath())) { 838 chosenTemplate = new File(nuxeoTemplates, nextToken); 839 } 840 if (includedTemplates.contains(chosenTemplate)) { 841 log.debug("Already included {}", nextToken); 842 continue; 843 } 844 if (!chosenTemplate.exists()) { 845 log.error( 846 "Template '{}' not found with relative or absolute path ({}). " 847 + "Check your {} parameter, and {} for included files.", 848 nextToken, chosenTemplate, PARAM_TEMPLATES_NAME, PARAM_INCLUDED_TEMPLATES); 849 continue; 850 } 851 includedTemplates.add(chosenTemplate); 852 if (isInvalidNuxeoDefaults(chosenTemplate)) { 853 log.warn("Ignore template (no default configuration): {}", nextToken); 854 continue; 855 } 856 857 Properties templateProperties = loadNuxeoDefaults(chosenTemplate); 858 String subTemplatesList = replaceEnvironmentVariables( 859 templateProperties.getProperty(PARAM_INCLUDED_TEMPLATES)); 860 if (StringUtils.isNotEmpty(subTemplatesList)) { 861 orderedTemplates.addAll(includeTemplates(subTemplatesList)); 862 } 863 // Load configuration from chosen templates 864 defaultConfig.putAll(templateProperties); 865 orderedTemplates.add(chosenTemplate); 866 log.log(logLevel, "Include template: {}", chosenTemplate::getPath); 867 } 868 return orderedTemplates; 869 } 870 871 /** 872 * Check for deprecated parameters 873 * 874 * @since 5.6 875 */ 876 protected void checkForDeprecatedParameters(Properties properties) { 877 @SuppressWarnings("rawtypes") 878 Enumeration userEnum = properties.propertyNames(); 879 while (userEnum.hasMoreElements()) { 880 String key = (String) userEnum.nextElement(); 881 if (parametersMigration.containsKey(key)) { 882 String value = properties.getProperty(key); 883 properties.setProperty(parametersMigration.get(key), value); 884 // Don't remove the deprecated key yet - more 885 // warnings but old things should keep working 886 // properties.remove(key); 887 if (!hideDeprecationWarnings) { 888 log.warn("Parameter {} is deprecated - please use {} instead", key, parametersMigration.get(key)); 889 } 890 } 891 } 892 } 893 894 public File getNuxeoHome() { 895 return nuxeoHome; 896 } 897 898 public File getNuxeoBinDir() { 899 return nuxeoBinDir; 900 } 901 902 /** 903 * @deprecated since 11.1, unused 904 */ 905 @Deprecated(since = "11.1") 906 public File getNuxeoDefaultConf() { 907 return new File(nuxeoTemplates, NUXEO_DEFAULT_CONF); 908 } 909 910 public List<File> getIncludedTemplates() { 911 return includedTemplates; 912 } 913 914 /** 915 * Save changed parameters in {@code nuxeo.conf}. This method does not check values in map. Use 916 * {@link #saveFilteredConfiguration(Map)} for parameters filtering. 917 * 918 * @param changedParameters Map of modified parameters 919 * @see #saveFilteredConfiguration(Map) 920 */ 921 public void saveConfiguration(Map<String, String> changedParameters) throws ConfigurationException { 922 // Keep generation true or once; switch false to once 923 saveConfiguration(changedParameters, false, true); 924 } 925 926 /** 927 * Save changed parameters in {@code nuxeo.conf} calculating templates if changedParameters contains a value for 928 * {@link #PARAM_TEMPLATE_DBNAME}. If a parameter value is empty ("" or null), then the property is unset. 929 * {@link #PARAM_TEMPLATES_NAME} and {@link #PARAM_FORCE_GENERATION} cannot be unset, but their value can be 930 * changed. 931 * <p> 932 * This method does not check values in map: use {@link #saveFilteredConfiguration(Map)} for parameters filtering. 933 * 934 * @param changedParameters Map of modified parameters 935 * @param setGenerationOnceToFalse If generation was on (true or once), then set it to false or not? 936 * @param setGenerationFalseToOnce If generation was off (false), then set it to once? 937 * @see #saveFilteredConfiguration(Map) 938 * @since 5.5 939 */ 940 public void saveConfiguration(Map<String, String> changedParameters, boolean setGenerationOnceToFalse, 941 boolean setGenerationFalseToOnce) throws ConfigurationException { 942 setOnceToFalse = setGenerationOnceToFalse; 943 setFalseToOnce = setGenerationFalseToOnce; 944 updateStoredConfig(); 945 String newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBNAME); 946 if (newDbTemplate != null) { 947 changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate)); 948 } 949 newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBSECONDARY_NAME); 950 if (newDbTemplate != null) { 951 changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate)); 952 } 953 if (changedParameters.containsValue(null) || changedParameters.containsValue("")) { 954 // There are properties to unset 955 Set<String> propertiesToUnset = new HashSet<>(); 956 for (Entry<String, String> entry : changedParameters.entrySet()) { 957 if (StringUtils.isEmpty(entry.getValue())) { 958 propertiesToUnset.add(entry.getKey()); 959 } 960 } 961 for (String key : propertiesToUnset) { 962 changedParameters.remove(key); 963 userConfig.remove(key); 964 } 965 } 966 userConfig.putAll(changedParameters); 967 writeConfiguration(); 968 updateStoredConfig(); 969 } 970 971 private void updateStoredConfig() { 972 if (storedConfig == null) { 973 storedConfig = new Properties(defaultConfig); 974 } else { 975 storedConfig.clear(); 976 } 977 storedConfig.putAll(userConfig); 978 } 979 980 /** 981 * Save changed parameters in {@code nuxeo.conf}, filtering parameters with {@link #getChangedParameters(Map)} 982 * 983 * @param changedParameters Maps of modified parameters 984 * @since 5.4.2 985 * @see #saveConfiguration(Map) 986 * @see #getChangedParameters(Map) 987 */ 988 public void saveFilteredConfiguration(Map<String, String> changedParameters) throws ConfigurationException { 989 Map<String, String> filteredParameters = getChangedParameters(changedParameters); 990 saveConfiguration(filteredParameters); 991 } 992 993 /** 994 * Filters given parameters including them only if (there was no previous value and new value is not empty/null) or 995 * (there was a previous value and it differs from the new value) 996 * 997 * @param changedParameters parameters to be filtered 998 * @return filtered map 999 * @since 5.4.2 1000 */ 1001 public Map<String, String> getChangedParameters(Map<String, String> changedParameters) { 1002 Map<String, String> filteredChangedParameters = new HashMap<>(); 1003 for (String key : changedParameters.keySet()) { 1004 String oldParam = getStoredConfig().getProperty(key); 1005 String newParam = changedParameters.get(key); 1006 if (newParam != null) { 1007 newParam = newParam.trim(); 1008 } 1009 if (oldParam == null && StringUtils.isNotEmpty(newParam) 1010 || oldParam != null && !oldParam.trim().equals(newParam)) { 1011 filteredChangedParameters.put(key, newParam); 1012 } 1013 } 1014 return filteredChangedParameters; 1015 } 1016 1017 private void writeConfiguration() throws ConfigurationException { 1018 final MessageDigest newContentDigest = DigestUtils.getMd5Digest(); 1019 StringWriter newContent = new StringWriter() { 1020 @Override 1021 public void write(String str) { 1022 if (str != null) { 1023 newContentDigest.update(str.getBytes()); 1024 } 1025 super.write(str); 1026 } 1027 }; 1028 // Copy back file content 1029 newContent.append(readConfiguration()); 1030 // Write changed parameters 1031 newContent.write(BOUNDARY_BEGIN + System.getProperty("line.separator")); 1032 for (Object o : new TreeSet<>(userConfig.keySet())) { 1033 String key = (String) o; 1034 // Ignore parameters already stored in newContent 1035 if (PARAM_FORCE_GENERATION.equals(key) || PARAM_TEMPLATES_NAME.equals(key)) { 1036 continue; 1037 } 1038 String oldValue = storedConfig.getProperty(key, ""); 1039 String newValue = userConfig.getRawProperty(key, ""); 1040 if (!newValue.equals(oldValue)) { 1041 newContent.write("#" + key + "=" + oldValue + System.getProperty("line.separator")); 1042 newContent.write(key + "=" + newValue + System.getProperty("line.separator")); 1043 } 1044 } 1045 newContent.write(BOUNDARY_END + System.getProperty("line.separator")); 1046 1047 // Write file only if content has changed 1048 if (!Hex.encodeHexString(newContentDigest.digest()).equals(currentConfigurationDigest)) { 1049 try (Writer writer = new FileWriter(nuxeoConf, false)) { 1050 writer.append(newContent.getBuffer()); 1051 } catch (IOException e) { 1052 throw new ConfigurationException("Error writing in " + nuxeoConf, e); 1053 } 1054 } 1055 } 1056 1057 private StringBuilder readConfiguration() throws ConfigurationException { 1058 // Will change templatesParam value instead of appending it 1059 String templatesParam = userConfig.getProperty(PARAM_TEMPLATES_NAME); 1060 Integer generationIndex = null, templatesIndex = null; 1061 List<String> newLines = new ArrayList<>(); 1062 try (BufferedReader reader = new BufferedReader(new FileReader(nuxeoConf))) { 1063 String line; 1064 MessageDigest digest = DigestUtils.getMd5Digest(); 1065 boolean onConfiguratorContent = false; 1066 while ((line = reader.readLine()) != null) { 1067 digest.update(line.getBytes()); 1068 if (!onConfiguratorContent) { 1069 if (!line.startsWith(BOUNDARY_BEGIN)) { 1070 if (line.startsWith(PARAM_FORCE_GENERATION)) { 1071 if (setOnceToFalse && onceGeneration) { 1072 line = PARAM_FORCE_GENERATION + "=false"; 1073 } 1074 if (setFalseToOnce && !forceGeneration) { 1075 line = PARAM_FORCE_GENERATION + "=once"; 1076 } 1077 if (generationIndex == null) { 1078 newLines.add(line); 1079 generationIndex = newLines.size() - 1; 1080 } else { 1081 newLines.set(generationIndex, line); 1082 } 1083 } else if (line.startsWith(PARAM_TEMPLATES_NAME)) { 1084 if (templatesParam != null) { 1085 line = PARAM_TEMPLATES_NAME + "=" + templatesParam; 1086 } 1087 if (templatesIndex == null) { 1088 newLines.add(line); 1089 templatesIndex = newLines.size() - 1; 1090 } else { 1091 newLines.set(templatesIndex, line); 1092 } 1093 } else { 1094 int equalIdx = line.indexOf("="); 1095 if (equalIdx < 1 || line.trim().startsWith("#")) { 1096 newLines.add(line); 1097 } else { 1098 String key = line.substring(0, equalIdx).trim(); 1099 if (userConfig.getProperty(key) != null) { 1100 newLines.add(line); 1101 } else { 1102 newLines.add("#" + line); 1103 } 1104 } 1105 } 1106 } else { 1107 // What must be written just before the BOUNDARY_BEGIN 1108 if (templatesIndex == null && templatesParam != null) { 1109 newLines.add(PARAM_TEMPLATES_NAME + "=" + templatesParam); 1110 templatesIndex = newLines.size() - 1; 1111 } 1112 onConfiguratorContent = true; 1113 } 1114 } else { 1115 if (!line.startsWith(BOUNDARY_END)) { 1116 int equalIdx = line.indexOf("="); 1117 if (line.startsWith("#" + PARAM_TEMPLATES_NAME) || line.startsWith(PARAM_TEMPLATES_NAME)) { 1118 // Backward compliance, it must be ignored 1119 continue; 1120 } 1121 if (equalIdx < 1) { // Ignore non-readable lines 1122 continue; 1123 } 1124 if (line.trim().startsWith("#")) { 1125 String key = line.substring(1, equalIdx).trim(); 1126 String value = line.substring(equalIdx + 1).trim(); 1127 getStoredConfig().setProperty(key, value); 1128 } else { 1129 String key = line.substring(0, equalIdx).trim(); 1130 String value = line.substring(equalIdx + 1).trim(); 1131 if (!value.equals(userConfig.getRawProperty(key))) { 1132 getStoredConfig().setProperty(key, value); 1133 } 1134 } 1135 } else { 1136 onConfiguratorContent = false; 1137 } 1138 } 1139 } 1140 reader.close(); 1141 currentConfigurationDigest = Hex.encodeHexString(digest.digest()); 1142 } catch (IOException e) { 1143 throw new ConfigurationException("Error reading " + nuxeoConf, e); 1144 } 1145 StringBuilder newContent = new StringBuilder(); 1146 for (String newLine : newLines) { 1147 newContent.append(newLine.trim()).append(System.lineSeparator()); 1148 } 1149 return newContent; 1150 } 1151 1152 /** 1153 * Extract a database template from the current list of templates. Return the last one if there are multiples. 1154 * 1155 * @see #rebuildTemplatesStr(String) 1156 */ 1157 public String extractDatabaseTemplateName() { 1158 return extractDbTemplateName(DB_LIST, PARAM_TEMPLATE_DBTYPE, PARAM_TEMPLATE_DBNAME, "unknown"); 1159 } 1160 1161 /** 1162 * Extract a NoSQL database template from the current list of templates. Return the last one if there are multiples. 1163 * 1164 * @see #rebuildTemplatesStr(String) 1165 * @since 8.1 1166 */ 1167 public String extractSecondaryDatabaseTemplateName() { 1168 return extractDbTemplateName(DB_SECONDARY_LIST, PARAM_TEMPLATE_DBSECONDARY_TYPE, 1169 PARAM_TEMPLATE_DBSECONDARY_NAME, null); 1170 } 1171 1172 private String extractDbTemplateName(List<String> knownDbList, String paramTemplateDbType, 1173 String paramTemplateDbName, String defaultTemplate) { 1174 String dbTemplate = defaultTemplate; 1175 boolean found = false; 1176 for (File templateFile : includedTemplates) { 1177 String template = templateFile.getName(); 1178 if (knownDbList.contains(template)) { 1179 dbTemplate = template; 1180 found = true; 1181 } 1182 } 1183 String dbType = userConfig.getProperty(paramTemplateDbType); 1184 if (!found && dbType != null) { 1185 log.warn(String.format("Didn't find a known database template in the list but " 1186 + "some template contributed a value for %s.", paramTemplateDbType)); 1187 dbTemplate = dbType; 1188 } 1189 if (dbTemplate != null && !dbTemplate.equals(dbType)) { 1190 if (dbType == null) { 1191 log.warn(String.format("Missing value for %s, using %s", paramTemplateDbType, dbTemplate)); 1192 userConfig.setProperty(paramTemplateDbType, dbTemplate); 1193 } else { 1194 log.debug(String.format("Different values between %s (%s) and %s (%s)", paramTemplateDbName, dbTemplate, 1195 paramTemplateDbType, dbType)); 1196 } 1197 } 1198 if (dbTemplate == null) { 1199 defaultConfig.remove(paramTemplateDbName); 1200 } else { 1201 defaultConfig.setProperty(paramTemplateDbName, dbTemplate); 1202 } 1203 return dbTemplate; 1204 } 1205 1206 /** 1207 * @return nuxeo.conf file used 1208 */ 1209 public File getNuxeoConf() { 1210 return nuxeoConf; 1211 } 1212 1213 /** 1214 * Delegate logs initialization to serverConfigurator instance 1215 * 1216 * @since 5.4.2 1217 */ 1218 public void initLogs() { 1219 serverConfigurator.initLogs(); 1220 } 1221 1222 /** 1223 * @return log directory 1224 * @since 5.4.2 1225 */ 1226 public File getLogDir() { 1227 return serverConfigurator.getLogDir(); 1228 } 1229 1230 /** 1231 * @return pid directory 1232 * @since 5.4.2 1233 */ 1234 public File getPidDir() { 1235 return serverConfigurator.getPidDir(); 1236 } 1237 1238 /** 1239 * @return Data directory 1240 * @since 5.4.2 1241 */ 1242 public File getDataDir() { 1243 return serverConfigurator.getDataDir(); 1244 } 1245 1246 /** 1247 * Create needed directories. Check existence of old paths. If old paths have been found and they cannot be upgraded 1248 * automatically, then upgrading message is logged and error thrown. 1249 * 1250 * @throws ConfigurationException If a deprecated directory has been detected. 1251 * @since 5.4.2 1252 * @see ServerConfigurator#verifyInstallation() 1253 */ 1254 public void verifyInstallation() throws ConfigurationException { 1255 checkJavaVersion(); 1256 getLogDir().mkdirs(); 1257 getPidDir().mkdirs(); 1258 getDataDir().mkdirs(); 1259 getTmpDir().mkdirs(); 1260 getPackagesDir().mkdirs(); 1261 checkAddressesAndPorts(); 1262 serverConfigurator.verifyInstallation(); 1263 backingServicesConfigurator.verifyInstallation(); 1264 } 1265 1266 /** 1267 * @return Marketplace packages directory 1268 * @since 5.9.4 1269 */ 1270 private File getPackagesDir() { 1271 return serverConfigurator.getPackagesDir(); 1272 } 1273 1274 /** 1275 * Check that the process is executed with a supported Java version. See 1276 * <a href="http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html">J2SE SDK/JRE Version String 1277 * Naming Convention</a> 1278 * 1279 * @since 5.6 1280 */ 1281 public void checkJavaVersion() throws ConfigurationException { 1282 String version = System.getProperty("java.version"); 1283 checkJavaVersion(version, COMPLIANT_JAVA_VERSIONS); 1284 } 1285 1286 /** 1287 * Check the java version compared to compliant ones. 1288 * 1289 * @param version the java version 1290 * @param compliantVersions the compliant java versions 1291 * @since 9.1 1292 */ 1293 protected static void checkJavaVersion(String version, String[] compliantVersions) throws ConfigurationException { 1294 // compliantVersions represents the java versions on which Nuxeo runs perfectly, so: 1295 // - if we run Nuxeo with a major java version present in compliantVersions and compatible with then this 1296 // method exits without error and without logging a warn message about loose compliance 1297 // - if we run Nuxeo with a major java version not present in compliantVersions but greater than once then 1298 // this method exits without error and logs a warn message about loose compliance 1299 // - if we run Nuxeo with a non valid java version then method exits with error 1300 // - if we run Nuxeo with a non valid java version and with jvmcheck=nofail property then method exits without 1301 // error and logs a warn message about loose compliance 1302 1303 // try to retrieve the closest compliant java version 1304 String lastCompliantVersion = null; 1305 for (String compliantVersion : compliantVersions) { 1306 if (checkJavaVersion(version, compliantVersion, false, false)) { 1307 // current compliant version is valid, go to next one 1308 lastCompliantVersion = compliantVersion; 1309 } else if (lastCompliantVersion != null) { 1310 // current compliant version is not valid, but we found a valid one earlier, 1st case 1311 return; 1312 } else if (checkJavaVersion(version, compliantVersion, true, true)) { 1313 // current compliant version is not valid, try to check java version with jvmcheck=nofail, 4th case 1314 // here we will log about loose compliance for the lower compliant java version 1315 return; 1316 } 1317 } 1318 // we might have lastCompliantVersion, unless nothing is valid against the current java version 1319 if (lastCompliantVersion != null) { 1320 // 2nd case: log about loose compliance if current major java version is greater than the greatest 1321 // compliant java version 1322 checkJavaVersion(version, lastCompliantVersion, false, true); 1323 return; 1324 } 1325 1326 // 3th case 1327 String message = String.format("Nuxeo requires Java %s (detected %s).", ArrayUtils.toString(compliantVersions), 1328 version); 1329 throw new ConfigurationException(message + " See '" + JVMCHECK_PROP + "' option to bypass version check."); 1330 } 1331 1332 /** 1333 * Checks the java version compared to the required one. 1334 * <p> 1335 * Loose compliance is assumed if the major version is greater than the required major version or a jvmcheck=nofail 1336 * flag is set. 1337 * 1338 * @param version the java version 1339 * @param requiredVersion the required java version 1340 * @param allowNoFailFlag if {@code true} then check jvmcheck=nofail flag to always have loose compliance 1341 * @param warnIfLooseCompliance if {@code true} then log a WARN if the is loose compliance 1342 * @return true if the java version is compliant (maybe loosely) with the required version 1343 * @since 8.4 1344 */ 1345 protected static boolean checkJavaVersion(String version, String requiredVersion, boolean allowNoFailFlag, 1346 boolean warnIfLooseCompliance) { 1347 allowNoFailFlag = allowNoFailFlag 1348 && JVMCHECK_NOFAIL.equalsIgnoreCase(System.getProperty(JVMCHECK_PROP, JVMCHECK_FAIL)); 1349 try { 1350 JVMVersion required = JVMVersion.parse(requiredVersion); 1351 JVMVersion actual = JVMVersion.parse(version); 1352 boolean compliant = actual.compareTo(required) >= 0; 1353 if (compliant && actual.compareTo(required, UpTo.MAJOR) == 0) { 1354 return true; 1355 } 1356 if (!compliant && !allowNoFailFlag) { 1357 return false; 1358 } 1359 // greater major version or noFail is present in system property, considered loosely compliant but may warn 1360 if (warnIfLooseCompliance) { 1361 log.warn(String.format("Nuxeo requires Java %s+ (detected %s).", requiredVersion, version)); 1362 } 1363 return true; 1364 } catch (java.text.ParseException cause) { 1365 if (allowNoFailFlag) { 1366 log.warn("Cannot check java version", cause); 1367 return true; 1368 } 1369 throw new IllegalArgumentException("Cannot check java version", cause); 1370 } 1371 } 1372 1373 /** 1374 * Checks the java version compared to the required one. 1375 * <p> 1376 * If major version is same as required major version and minor is greater or equal, it is compliant. 1377 * <p> 1378 * If major version is greater than required major version, it is compliant. 1379 * 1380 * @param version the java version 1381 * @param requiredVersion the required java version 1382 * @return true if the java version is compliant with the required version 1383 * @since 8.4 1384 */ 1385 public static boolean checkJavaVersion(String version, String requiredVersion) { 1386 return checkJavaVersion(version, requiredVersion, false, false); 1387 } 1388 1389 /** 1390 * Will check the configured addresses are reachable and Nuxeo required ports are available on those addresses. 1391 * Server specific implementations should override this method in order to check for server specific ports. 1392 * {@link #PARAM_BIND_ADDRESS} must be set before. 1393 * 1394 * @since 5.5 1395 * @see ServerConfigurator#verifyInstallation() 1396 */ 1397 public void checkAddressesAndPorts() throws ConfigurationException { 1398 InetAddress bindAddress = getBindAddress(); 1399 // Sanity check 1400 if (bindAddress.isMulticastAddress()) { 1401 throw new ConfigurationException("Multicast address won't work: " + bindAddress); 1402 } 1403 checkAddressReachable(bindAddress); 1404 checkPortAvailable(bindAddress, Integer.parseInt(userConfig.getProperty(PARAM_HTTP_PORT))); 1405 } 1406 1407 /** 1408 * Checks the userConfig bind address is not 0.0.0.0 and replaces it with 127.0.0.1 if needed 1409 * 1410 * @return the userConfig bind address if not 0.0.0.0 else 127.0.0.1 1411 * @since 5.7 1412 */ 1413 public InetAddress getBindAddress() throws ConfigurationException { 1414 return getBindAddress(userConfig.getProperty(PARAM_BIND_ADDRESS)); 1415 } 1416 1417 /** 1418 * Checks hostName bind address is not 0.0.0.0 and replaces it with 127.0.0.1 if needed 1419 * 1420 * @param hostName the hostname of Nuxeo server (works also with the IP) 1421 * @return the bind address matching hostName parameter if not 0.0.0.0 else 127.0.0.1 1422 * @since 9.2 1423 */ 1424 public static InetAddress getBindAddress(String hostName) throws ConfigurationException { 1425 InetAddress bindAddress; 1426 try { 1427 bindAddress = InetAddress.getByName(hostName); 1428 if (bindAddress.isAnyLocalAddress()) { 1429 boolean preferIPv6 = "false".equals(System.getProperty("java.net.preferIPv4Stack")) 1430 && "true".equals(System.getProperty("java.net.preferIPv6Addresses")); 1431 bindAddress = preferIPv6 ? InetAddress.getByName("::1") : InetAddress.getByName("127.0.0.1"); 1432 log.debug("Bind address is \"ANY\", using local address instead: {}", bindAddress); 1433 } 1434 log.debug("Configured bind address: {}", bindAddress); 1435 } catch (UnknownHostException e) { 1436 throw new ConfigurationException(e); 1437 } 1438 return bindAddress; 1439 } 1440 1441 /** 1442 * @param address address to check for availability 1443 * @since 5.5 1444 */ 1445 public static void checkAddressReachable(InetAddress address) throws ConfigurationException { 1446 try { 1447 log.debug("Checking availability of " + address); 1448 address.isReachable(ADDRESS_PING_TIMEOUT); 1449 } catch (IllegalArgumentException | IOException e) { 1450 throw new ConfigurationException("Unreachable bind address " + address, e); 1451 } 1452 } 1453 1454 /** 1455 * Checks if port is available on given address. 1456 * 1457 * @param port port to check for availability 1458 * @throws ConfigurationException Throws an exception if address is unavailable. 1459 * @since 5.5 1460 */ 1461 public static void checkPortAvailable(InetAddress address, int port) throws ConfigurationException { 1462 if (port == 0 || port == -1) { 1463 log.warn("Port is set to {} - assuming it is disabled - skipping availability check", port); 1464 return; 1465 } 1466 if (port < MIN_PORT || port > MAX_PORT) { 1467 throw new IllegalArgumentException("Invalid port: " + port); 1468 } 1469 log.debug("Checking availability of port {} on address {}", port, address); 1470 try (ServerSocket socketTCP = new ServerSocket(port, 0, address)) { 1471 socketTCP.setReuseAddress(true); 1472 } catch (IOException e) { 1473 throw new ConfigurationException(e.getMessage() + ": " + address + ":" + port, e); 1474 } 1475 } 1476 1477 /** 1478 * @return Temporary directory 1479 */ 1480 public File getTmpDir() { 1481 return serverConfigurator.getTmpDir(); 1482 } 1483 1484 /** 1485 * @return Log files produced by Log4J configuration without loading this configuration instead of current active 1486 * one. 1487 * @since 5.4.2 1488 */ 1489 public List<String> getLogFiles() { 1490 File log4jConfFile = serverConfigurator.getLogConfFile(); 1491 System.setProperty(Environment.NUXEO_LOG_DIR, getLogDir().getPath()); 1492 return Log4JHelper.getFileAppendersFileNames(log4jConfFile); 1493 } 1494 1495 /** 1496 * Rebuild a templates string for use in nuxeo.conf 1497 * 1498 * @param dbTemplate database template to use instead of current one 1499 * @return new templates string using given dbTemplate 1500 * @since 5.4.2 1501 * @see #extractDatabaseTemplateName() 1502 * @see #changeDBTemplate(String) 1503 * @see #changeTemplates(String) 1504 */ 1505 public String rebuildTemplatesStr(String dbTemplate) { 1506 List<String> templatesList = new ArrayList<>(asList(templates.split(TEMPLATE_SEPARATOR))); 1507 String currentDBTemplate = null; 1508 if (DB_LIST.contains(dbTemplate)) { 1509 currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBNAME); 1510 if (currentDBTemplate == null) { 1511 currentDBTemplate = extractDatabaseTemplateName(); 1512 } 1513 } else if (DB_SECONDARY_LIST.contains(dbTemplate)) { 1514 currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBSECONDARY_NAME); 1515 if (currentDBTemplate == null) { 1516 currentDBTemplate = extractSecondaryDatabaseTemplateName(); 1517 } 1518 if ("none".equals(dbTemplate)) { 1519 dbTemplate = null; 1520 } 1521 } 1522 int dbIdx = templatesList.indexOf(currentDBTemplate); 1523 if (dbIdx < 0) { 1524 if (dbTemplate == null) { 1525 return templates; 1526 } 1527 // current db template is implicit => set the new one 1528 templatesList.add(dbTemplate); 1529 } else if (dbTemplate == null) { 1530 // current db template is explicit => remove it 1531 templatesList.remove(dbIdx); 1532 } else { 1533 // current db template is explicit => replace it 1534 templatesList.set(dbIdx, dbTemplate); 1535 } 1536 return replaceEnvironmentVariables(String.join(TEMPLATE_SEPARATOR, templatesList)); 1537 } 1538 1539 /** 1540 * @return Nuxeo config directory 1541 * @since 5.4.2 1542 */ 1543 public File getConfigDir() { 1544 return serverConfigurator.getConfigDir(); 1545 } 1546 1547 /** 1548 * @return Nuxeo runtime home 1549 */ 1550 public File getRuntimeHome() { 1551 return serverConfigurator.getRuntimeHome(); 1552 } 1553 1554 /** 1555 * @since 5.4.2 1556 * @return true if there's an install in progress 1557 */ 1558 public boolean isInstallInProgress() { 1559 return getInstallFile().exists(); 1560 } 1561 1562 /** 1563 * @return File pointing to the directory containing the marketplace packages included in the distribution 1564 * @since 5.6 1565 * @deprecated since 11.1, Nuxeo Wizard has been removed. 1566 */ 1567 @Deprecated(since = "11.1") 1568 public File getDistributionMPDir() { 1569 String mpDir = userConfig.getProperty(PARAM_MP_DIR, DISTRIBUTION_MP_DIR); 1570 return new File(getNuxeoHome(), mpDir); 1571 } 1572 1573 /** 1574 * @return Install/upgrade file 1575 * @since 5.4.1 1576 */ 1577 public File getInstallFile() { 1578 return new File(serverConfigurator.getDataDir(), INSTALL_AFTER_RESTART); 1579 } 1580 1581 /** 1582 * Add template(s) to the {@link #PARAM_TEMPLATES_NAME} list if not already present 1583 * 1584 * @param templatesToAdd Comma separated templates to add 1585 * @since 5.5 1586 */ 1587 public void addTemplate(String templatesToAdd) throws ConfigurationException { 1588 List<String> templatesList = getTemplateList(); 1589 List<String> templatesToAddList = asList(templatesToAdd.split(TEMPLATE_SEPARATOR)); 1590 if (templatesList.addAll(templatesToAddList)) { 1591 String newTemplatesStr = String.join(TEMPLATE_SEPARATOR, templatesList); 1592 Map<String, String> parametersToSave = new HashMap<>(); 1593 parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr); 1594 saveFilteredConfiguration(parametersToSave); 1595 changeTemplates(newTemplatesStr); 1596 } 1597 } 1598 1599 /** 1600 * Return the list of templates. 1601 * 1602 * @since 9.2 1603 */ 1604 public List<String> getTemplateList() { 1605 String currentTemplatesStr = userConfig.getProperty(PARAM_TEMPLATES_NAME); 1606 1607 return Stream.of(replaceEnvironmentVariables(currentTemplatesStr).split(TEMPLATE_SEPARATOR)) 1608 .collect(Collectors.toList()); 1609 1610 } 1611 1612 /** 1613 * Remove template(s) from the {@link #PARAM_TEMPLATES_NAME} list 1614 * 1615 * @param templatesToRm Comma separated templates to remove 1616 * @since 5.5 1617 */ 1618 public void rmTemplate(String templatesToRm) throws ConfigurationException { 1619 List<String> templatesList = getTemplateList(); 1620 List<String> templatesToRmList = asList(templatesToRm.split(TEMPLATE_SEPARATOR)); 1621 if (templatesList.removeAll(templatesToRmList)) { 1622 String newTemplatesStr = String.join(TEMPLATE_SEPARATOR, templatesList); 1623 Map<String, String> parametersToSave = new HashMap<>(); 1624 parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr); 1625 saveFilteredConfiguration(parametersToSave); 1626 changeTemplates(newTemplatesStr); 1627 } 1628 } 1629 1630 /** 1631 * Set a property in nuxeo configuration 1632 * 1633 * @return The old value 1634 * @since 5.5 1635 */ 1636 public String setProperty(String key, String value) throws ConfigurationException { 1637 String oldValue = getStoredConfig().getProperty(key); 1638 if (PARAM_TEMPLATES_NAME.equals(key)) { 1639 templates = StringUtils.isBlank(value) ? null : value; 1640 } 1641 HashMap<String, String> newParametersToSave = new HashMap<>(); 1642 newParametersToSave.put(key, value); 1643 saveFilteredConfiguration(newParametersToSave); 1644 setBasicConfiguration(); 1645 return oldValue; 1646 } 1647 1648 /** 1649 * Set properties in nuxeo configuration 1650 * 1651 * @return The old values 1652 * @since 7.4 1653 */ 1654 public Map<String, String> setProperties(Map<String, String> newParametersToSave) throws ConfigurationException { 1655 Map<String, String> oldValues = new HashMap<>(); 1656 for (String key : newParametersToSave.keySet()) { 1657 oldValues.put(key, getStoredConfig().getProperty(key)); 1658 if (PARAM_TEMPLATES_NAME.equals(key)) { 1659 String value = newParametersToSave.get(key); 1660 templates = StringUtils.isBlank(value) ? null : value; 1661 } 1662 } 1663 saveFilteredConfiguration(newParametersToSave); 1664 setBasicConfiguration(); 1665 return oldValues; 1666 } 1667 1668 /** 1669 * Set properties in the given template, if it exists 1670 * 1671 * @return The old values 1672 * @since 7.4 1673 */ 1674 public Map<String, String> setProperties(String template, Map<String, String> newParametersToSave) 1675 throws ConfigurationException, IOException { 1676 File templateDir = getTemplateDirectory(template); 1677 File templateConf; 1678 String nuxeoEnv = getEnvironment(NUXEO_ENVIRONMENT, ""); 1679 if (nuxeoEnv.isBlank()) { 1680 templateConf = new File(templateDir, NUXEO_DEFAULT_CONF); 1681 } else { 1682 templateConf = new File(templateDir, String.format(NUXEO_ENVIRONMENT_CONF_FORMAT, nuxeoEnv)); 1683 } 1684 Properties templateProperties = loadTrimmedProperties(templateConf); 1685 Map<String, String> oldValues = new HashMap<>(); 1686 StringBuilder newContent = new StringBuilder(); 1687 try (BufferedReader reader = new BufferedReader(new FileReader(templateConf))) { 1688 String line = reader.readLine(); 1689 if (line != null && line.startsWith("## DO NOT EDIT THIS FILE")) { 1690 throw new ConfigurationException("The template states in its header that it must not be modified."); 1691 } 1692 while (line != null) { 1693 int equalIdx = line.indexOf("="); 1694 if (equalIdx < 1 || line.trim().startsWith("#")) { 1695 newContent.append(line).append(System.getProperty("line.separator")); 1696 } else { 1697 String key = line.substring(0, equalIdx).trim(); 1698 if (newParametersToSave.containsKey(key)) { 1699 newContent.append(key) 1700 .append("=") 1701 .append(newParametersToSave.get(key)) 1702 .append(System.getProperty("line.separator")); 1703 } else { 1704 newContent.append(line).append(System.getProperty("line.separator")); 1705 } 1706 } 1707 line = reader.readLine(); 1708 } 1709 } 1710 for (String key : newParametersToSave.keySet()) { 1711 if (templateProperties.containsKey(key)) { 1712 oldValues.put(key, templateProperties.getProperty(key)); 1713 } else { 1714 newContent.append(key).append("=").append(newParametersToSave.get(key)).append(System.lineSeparator()); 1715 } 1716 } 1717 try (BufferedWriter writer = new BufferedWriter(new FileWriter(templateConf))) { 1718 writer.append(newContent.toString()); 1719 } 1720 setBasicConfiguration(); 1721 return oldValues; 1722 } 1723 1724 /** 1725 * Check driver availability and database connection 1726 * 1727 * @param databaseTemplate Nuxeo database template 1728 * @param dbName nuxeo.db.name parameter in nuxeo.conf 1729 * @param dbUser nuxeo.db.user parameter in nuxeo.conf 1730 * @param dbPassword nuxeo.db.password parameter in nuxeo.conf 1731 * @param dbHost nuxeo.db.host parameter in nuxeo.conf 1732 * @param dbPort nuxeo.db.port parameter in nuxeo.conf 1733 * @since 5.6 1734 */ 1735 public void checkDatabaseConnection(String databaseTemplate, String dbName, String dbUser, String dbPassword, 1736 String dbHost, String dbPort) throws IOException, DatabaseDriverException, SQLException { 1737 File databaseTemplateDir = new File(nuxeoTemplates, databaseTemplate); 1738 Properties templateProperties = loadNuxeoDefaults(databaseTemplateDir); 1739 String classname, connectionUrl; 1740 // check if value is set in nuxeo.conf 1741 if (userConfig.containsKey(PARAM_DB_DRIVER)) { 1742 classname = (String) userConfig.get(PARAM_DB_DRIVER); 1743 } else { 1744 classname = templateProperties.getProperty(PARAM_DB_DRIVER); 1745 } 1746 if (userConfig.containsKey(PARAM_DB_JDBC_URL)) { 1747 connectionUrl = (String) userConfig.get(PARAM_DB_JDBC_URL); 1748 } else { 1749 connectionUrl = templateProperties.getProperty(PARAM_DB_JDBC_URL); 1750 } 1751 // Load driver class from template or default lib directory 1752 Driver driver = lookupDriver(databaseTemplate, databaseTemplateDir, classname); 1753 // Test db connection 1754 DriverManager.registerDriver(driver); 1755 Properties ttProps = new Properties(userConfig); 1756 ttProps.put(PARAM_DB_HOST, dbHost); 1757 ttProps.put(PARAM_DB_PORT, dbPort); 1758 ttProps.put(PARAM_DB_NAME, dbName); 1759 ttProps.put(PARAM_DB_USER, dbUser); 1760 ttProps.put(PARAM_DB_PWD, dbPassword); 1761 TextTemplate tt = new TextTemplate(ttProps); 1762 String url = tt.processText(connectionUrl); 1763 Properties conProps = new Properties(); 1764 conProps.put("user", dbUser); 1765 conProps.put("password", dbPassword); 1766 log.debug("Testing URL " + url + " with " + conProps); 1767 Connection con = driver.connect(url, conProps); 1768 con.close(); 1769 } 1770 1771 /** 1772 * Build an {@link URLClassLoader} for the given databaseTemplate looking in the templates directory and in the 1773 * server lib directory, then looks for a driver 1774 * 1775 * @param classname Driver class name, defined by {@link #PARAM_DB_DRIVER} 1776 * @return Driver driver if found, else an Exception must have been raised. 1777 * @throws DatabaseDriverException If there was an error when trying to instantiate the driver. 1778 * @since 5.6 1779 */ 1780 private Driver lookupDriver(String databaseTemplate, File databaseTemplateDir, String classname) 1781 throws DatabaseDriverException { 1782 File[] files = ArrayUtils.addAll( // 1783 new File(databaseTemplateDir, "lib").listFiles(), // 1784 serverConfigurator.getServerLibDir().listFiles()); 1785 List<URL> urlsList = new ArrayList<>(); 1786 if (files != null) { 1787 for (File file : files) { 1788 if (file.getName().endsWith("jar")) { 1789 try { 1790 urlsList.add(new URL("jar:file:" + file.getPath() + "!/")); 1791 log.debug("Added " + file.getPath()); 1792 } catch (MalformedURLException e) { 1793 log.error(e); 1794 } 1795 } 1796 } 1797 } 1798 URLClassLoader ucl = new URLClassLoader(urlsList.toArray(new URL[0])); 1799 try { 1800 return (Driver) Class.forName(classname, true, ucl).getDeclaredConstructor().newInstance(); 1801 } catch (ReflectiveOperationException e) { 1802 throw new DatabaseDriverException(e); 1803 } 1804 } 1805 1806 /** 1807 * @since 5.6 1808 * @return an {@link Environment} initialized with a few basics 1809 */ 1810 public Environment getEnv() { 1811 /* 1812 * It could be useful to initialize DEFAULT env in {@link #setBasicConfiguration()}... For now, the generated 1813 * {@link Environment} is not static. 1814 */ 1815 if (env == null) { 1816 env = new Environment(getRuntimeHome()); 1817 File distribFile = new File(new File(nuxeoHome, TEMPLATES), "common/config/distribution.properties"); 1818 if (distribFile.exists()) { 1819 try { 1820 env.loadProperties(loadTrimmedProperties(distribFile)); 1821 } catch (IOException e) { 1822 log.error(e); 1823 } 1824 } 1825 env.loadProperties(userConfig); 1826 env.setServerHome(getNuxeoHome()); 1827 env.init(); 1828 env.setData(userConfig.getProperty(Environment.NUXEO_DATA_DIR, "data")); 1829 env.setLog(userConfig.getProperty(Environment.NUXEO_LOG_DIR, "logs")); 1830 env.setTemp(userConfig.getProperty(Environment.NUXEO_TMP_DIR, "tmp")); 1831 env.setPath(Environment.NUXEO_MP_DIR, getPackagesDir(), env.getServerHome()); 1832 } 1833 return env; 1834 } 1835 1836 /** 1837 * @since 10.2 1838 * @param propsFile Properties file 1839 * @return String with the charset encoding for this file 1840 */ 1841 public static Charset checkFileCharset(File propsFile) throws IOException { 1842 List<Charset> charsetsToBeTested = asList(US_ASCII, UTF_8, ISO_8859_1); 1843 for (Charset charsetTest : charsetsToBeTested) { 1844 CharsetDecoder decoder = charsetTest.newDecoder(); 1845 decoder.reset(); 1846 1847 boolean identified = true; // assume the charset is this one, until it is not ! 1848 try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(propsFile))) { 1849 byte[] buffer = new byte[512]; 1850 while (input.read(buffer) != -1 && identified) { 1851 try { 1852 decoder.decode(ByteBuffer.wrap(buffer)); 1853 identified = true; 1854 } catch (CharacterCodingException e) { 1855 identified = false; 1856 } 1857 } 1858 } 1859 if (identified) { 1860 return charsetTest; 1861 } 1862 } 1863 return null; 1864 } 1865 1866 /** 1867 * Loads the {@code nuxeo.defaults} and {@code nuxeo.NUXEO_ENVIRONMENT} files. 1868 * <p> 1869 * This method assumes {@code nuxeo.defaults} exists and is readable. 1870 */ 1871 protected Properties loadNuxeoDefaults(File directory) throws IOException { 1872 // load nuxeo.defaults 1873 Properties properties = loadTrimmedProperties(new File(directory, NUXEO_DEFAULT_CONF)); 1874 // load nuxeo.NUXEO_ENVIRONMENT 1875 File nuxeoDefaultsEnv = new File(directory, getNuxeoEnvironmentConfName()); 1876 if (nuxeoDefaultsEnv.exists()) { 1877 loadTrimmedProperties(properties, nuxeoDefaultsEnv); 1878 } 1879 Properties targetProps = new Properties(); 1880 properties.stringPropertyNames() 1881 .forEach(p -> targetProps.put(p, replaceEnvironmentVariables(properties.getProperty(p)))); 1882 return targetProps; 1883 } 1884 1885 /** 1886 * @since 5.6 1887 * @param propsFile Properties file 1888 * @return new Properties containing trimmed keys and values read in {@code propsFile} 1889 */ 1890 public static Properties loadTrimmedProperties(File propsFile) throws IOException { 1891 return loadTrimmedProperties(new Properties(), propsFile); 1892 } 1893 1894 protected static Properties loadTrimmedProperties(Properties props, File propsFile) throws IOException { 1895 Charset charset = checkFileCharset(propsFile); 1896 if (charset == null) { 1897 throw new IOException("Can't identify input file charset for " + propsFile.getName()); 1898 } 1899 log.debug("Opening {} in {}", propsFile::getName, charset::name); 1900 try (InputStreamReader propsIS = new InputStreamReader(new FileInputStream(propsFile), charset)) { 1901 loadTrimmedProperties(props, propsIS); 1902 } 1903 return props; 1904 } 1905 1906 /** 1907 * @since 5.6 1908 * @param props Properties object to be filled 1909 * @param propsIS Properties InputStream 1910 */ 1911 public static void loadTrimmedProperties(Properties props, InputStreamReader propsIS) throws IOException { 1912 if (props == null) { 1913 return; 1914 } 1915 Properties p = new Properties(); 1916 p.load(propsIS); 1917 @SuppressWarnings("unchecked") 1918 Enumeration<String> pEnum = (Enumeration<String>) p.propertyNames(); 1919 while (pEnum.hasMoreElements()) { 1920 String key = pEnum.nextElement(); 1921 String value = p.getProperty(key); 1922 props.put(key.trim(), value.trim()); 1923 } 1924 } 1925 1926 /** 1927 * @return The generated properties file with dumped configuration. 1928 * @since 5.6 1929 */ 1930 public File getDumpedConfig() { 1931 return new File(getConfigDir(), CONFIGURATION_PROPERTIES); 1932 } 1933 1934 /** 1935 * Build a {@link Hashtable} which contains environment properties to instantiate a {@link InitialDirContext} 1936 * 1937 * @since 6.0 1938 */ 1939 public Hashtable<Object, Object> getContextEnv(String ldapUrl, String bindDn, String bindPassword, 1940 boolean checkAuthentication) { 1941 Hashtable<Object, Object> contextEnv = new Hashtable<>(); 1942 contextEnv.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 1943 contextEnv.put("com.sun.jndi.ldap.connect.timeout", "10000"); 1944 contextEnv.put(javax.naming.Context.PROVIDER_URL, ldapUrl); 1945 if (checkAuthentication) { 1946 contextEnv.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple"); 1947 contextEnv.put(javax.naming.Context.SECURITY_PRINCIPAL, bindDn); 1948 contextEnv.put(javax.naming.Context.SECURITY_CREDENTIALS, bindPassword); 1949 } 1950 return contextEnv; 1951 } 1952 1953 /** 1954 * Check if the LDAP parameters are correct to bind to a LDAP server. if authenticate argument is true, it will also 1955 * check if the authentication against the LDAP server succeeds 1956 * 1957 * @param authenticate Indicates if authentication against LDAP should be checked. 1958 * @since 6.0 1959 */ 1960 public void checkLdapConnection(String ldapUrl, String ldapBindDn, String ldapBindPwd, boolean authenticate) 1961 throws NamingException { 1962 checkLdapConnection(getContextEnv(ldapUrl, ldapBindDn, ldapBindPwd, authenticate)); 1963 } 1964 1965 /** 1966 * @param contextEnv Environment properties to build a {@link InitialDirContext} 1967 * @since 6.0 1968 */ 1969 public void checkLdapConnection(Hashtable<Object, Object> contextEnv) throws NamingException { 1970 DirContext dirContext = new InitialDirContext(contextEnv); 1971 dirContext.close(); 1972 } 1973 1974 /** 1975 * @return a {@link Crypto} instance initialized with the configuration parameters 1976 * @since 7.4 1977 * @see Crypto 1978 */ 1979 public Crypto getCrypto() { 1980 return userConfig.getCrypto(); 1981 } 1982 1983 /** 1984 * @param template path to configuration template directory 1985 * @return A {@code nuxeo.defaults} file if it exists. 1986 * @throws ConfigurationException if the template file is not found. 1987 * @since 7.4 1988 * @deprecated since 11.1, there's several configuration files, use {@link #getTemplateDirectory(String)} instead 1989 */ 1990 @Deprecated(since = "11.1") 1991 public File getTemplateConf(String template) throws ConfigurationException { 1992 return new File(getTemplateDirectory(template), NUXEO_DEFAULT_CONF); 1993 } 1994 1995 /** 1996 * @throws ConfigurationException if the template directory is not valid 1997 * @since 11.1 1998 */ 1999 public File getTemplateDirectory(String template) throws ConfigurationException { 2000 // look for template declared with a path 2001 File templateDir = new File(template); 2002 if (!templateDir.isAbsolute()) { 2003 // look for template under nuxeoBinDir 2004 templateDir = new File(System.getProperty("user.dir"), template); 2005 if (isInvalidNuxeoDefaults(templateDir)) { 2006 templateDir = new File(nuxeoTemplates, template); 2007 } 2008 } 2009 if (isInvalidNuxeoDefaults(templateDir)) { 2010 throw new ConfigurationException("Template not found: " + template); 2011 } 2012 return templateDir; 2013 } 2014 2015 protected boolean isInvalidNuxeoDefaults(File templateDir) { 2016 return !templateDir.exists() || !new File(templateDir, NUXEO_DEFAULT_CONF).exists(); 2017 } 2018 2019 /** 2020 * Gets the Java options with 'nuxeo.*' properties substituted. It enables usage of property like ${nuxeo.log.dir} 2021 * inside JAVA_OPTS. 2022 * 2023 * @return the Java options string. 2024 * @deprecated Since 9.3. Use {@link #getJavaOptsString()} instead. 2025 */ 2026 @Deprecated 2027 @SuppressWarnings("unused") 2028 protected String getJavaOpts(String key, String value) { 2029 return getJavaOptsString(); 2030 } 2031 2032 /** 2033 * Gets the Java options defined in Nuxeo configuration files, e.g. {@code bin/nuxeo.conf} and 2034 * {@code bin/nuxeoctl}. 2035 * 2036 * @return the Java options. 2037 * @since 9.3 2038 */ 2039 public List<String> getJavaOpts(Function<String, String> mapper) { 2040 return Arrays.stream(JAVA_OPTS_PATTERN.split(System.getProperty(JAVA_OPTS_PROP, ""))) 2041 .map(option -> StringSubstitutor.replace(option, getUserConfig())) 2042 .map(mapper) 2043 .collect(Collectors.toList()); 2044 } 2045 2046 /** 2047 * @return the Java options string. 2048 * @since 9.3 2049 * @see #getJavaOpts(Function) 2050 */ 2051 protected String getJavaOptsString() { 2052 return String.join(" ", getJavaOpts(Function.identity())); 2053 } 2054 2055 /** 2056 * @return the value of an environment variable 2057 * @since 9.1 2058 * @apiNote exists to be overridden by tests 2059 */ 2060 protected String getEnvironment(String key) { 2061 return System.getenv(key); 2062 } 2063 2064 /** 2065 * @return the value of an environment variable 2066 * @since 11.1 2067 * @see #getEnvironment(String) 2068 */ 2069 protected String getEnvironment(String key, String defaultValue) { 2070 return Objects.requireNonNullElse(getEnvironment(key), defaultValue); 2071 } 2072 2073 /** 2074 * @return the nuxeo.defaults file for current {@code NUXEO_ENVIRONMENT} 2075 * @since 11.1 2076 */ 2077 protected String getNuxeoEnvironmentConfName() { 2078 return String.format(NUXEO_ENVIRONMENT_CONF_FORMAT, getEnvironment(NUXEO_ENVIRONMENT)); 2079 } 2080}