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