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