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