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