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