001/* 002 * (C) Copyright 2010-2017 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Julien Carsique 018 * 019 */ 020 021package org.nuxeo.launcher.config; 022 023import java.io.BufferedReader; 024import java.io.BufferedWriter; 025import java.io.File; 026import java.io.FileNotFoundException; 027import java.io.FileOutputStream; 028import java.io.FileReader; 029import java.io.FilenameFilter; 030import java.io.IOException; 031import java.io.OutputStreamWriter; 032import java.net.MalformedURLException; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Enumeration; 036import java.util.HashSet; 037import java.util.List; 038import java.util.Map; 039import java.util.Properties; 040import java.util.Set; 041import java.util.StringTokenizer; 042import java.util.TreeSet; 043 044import org.apache.commons.io.FileUtils; 045import org.apache.commons.io.IOUtils; 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.apache.log4j.BasicConfigurator; 049import org.apache.log4j.xml.DOMConfigurator; 050import org.nuxeo.common.Environment; 051import org.nuxeo.common.codec.Crypto; 052import org.nuxeo.common.codec.CryptoProperties; 053import org.nuxeo.common.utils.TextTemplate; 054import org.nuxeo.connect.update.LocalPackage; 055import org.nuxeo.launcher.info.ConfigurationInfo; 056import org.nuxeo.launcher.info.DistributionInfo; 057import org.nuxeo.launcher.info.InstanceInfo; 058import org.nuxeo.launcher.info.KeyValueInfo; 059import org.nuxeo.launcher.info.PackageInfo; 060 061import freemarker.template.TemplateException; 062 063/** 064 * @author jcarsique 065 */ 066public abstract class ServerConfigurator { 067 068 protected static final Log log = LogFactory.getLog(ServerConfigurator.class); 069 070 protected final ConfigurationGenerator generator; 071 072 protected File dataDir = null; 073 074 protected File logDir = null; 075 076 protected File pidDir = null; 077 078 protected File tmpDir = null; 079 080 protected File packagesDir = null; 081 082 /** 083 * @since 5.4.2 084 */ 085 public static final List<String> NUXEO_SYSTEM_PROPERTIES = Arrays.asList("nuxeo.conf", "nuxeo.home", "log.id"); 086 087 protected static final String DEFAULT_CONTEXT_NAME = "/nuxeo"; 088 089 /** @since 9.3 */ 090 public static final String JAVA_OPTS = "JAVA_OPTS"; 091 092 private static final String NEW_FILES = ConfigurationGenerator.TEMPLATES + File.separator + "files.list"; 093 094 /** 095 * @since 5.4.2 096 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_LOG_DIR} instead. 097 */ 098 @Deprecated 099 public static final String DEFAULT_LOG_DIR = org.nuxeo.common.Environment.DEFAULT_LOG_DIR; 100 101 /** 102 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_DATA_DIR} instead. 103 */ 104 @Deprecated 105 public static final String DEFAULT_DATA_DIR = org.nuxeo.common.Environment.DEFAULT_DATA_DIR; 106 107 /** 108 * @since 5.4.2 109 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_TMP_DIR} instead. 110 */ 111 @Deprecated 112 public static final String DEFAULT_TMP_DIR = org.nuxeo.common.Environment.DEFAULT_TMP_DIR; 113 114 public ServerConfigurator(ConfigurationGenerator configurationGenerator) { 115 generator = configurationGenerator; 116 } 117 118 /** 119 * @return true if server configuration files already exist 120 */ 121 abstract boolean isConfigured(); 122 123 /** 124 * Generate configuration files from templates and given configuration parameters 125 * 126 * @param config Properties with configuration parameters for template replacement 127 */ 128 protected void parseAndCopy(Properties config) throws IOException, TemplateException, ConfigurationException { 129 // FilenameFilter for excluding "nuxeo.defaults" files from copy 130 final FilenameFilter filter = (dir, name) -> !ConfigurationGenerator.NUXEO_DEFAULT_CONF.equals(name); 131 final TextTemplate templateParser = new TextTemplate(config); 132 templateParser.setKeepEncryptedAsVar(true); 133 templateParser.setTrim(true); 134 templateParser.setTextParsingExtensions(config.getProperty( 135 ConfigurationGenerator.PARAM_TEMPLATES_PARSING_EXTENSIONS, "xml,properties,nx")); 136 templateParser.setFreemarkerParsingExtensions(config.getProperty( 137 ConfigurationGenerator.PARAM_TEMPLATES_FREEMARKER_EXTENSIONS, "nxftl")); 138 139 deleteTemplateFiles(); 140 // add included templates directories 141 List<String> newFilesList = new ArrayList<>(); 142 for (File includedTemplate : generator.getIncludedTemplates()) { 143 File[] listFiles = includedTemplate.listFiles(filter); 144 if (listFiles != null) { 145 String templateName = includedTemplate.getName(); 146 log.debug(String.format("Parsing %s... %s", templateName, Arrays.toString(listFiles))); 147 // Check for deprecation 148 Boolean isDeprecated = Boolean.valueOf(config.getProperty(templateName + ".deprecated")); 149 if (isDeprecated) { 150 log.warn("WARNING: Template " + templateName + " is deprecated."); 151 String deprecationMessage = config.getProperty(templateName + ".deprecation"); 152 if (deprecationMessage != null) { 153 log.warn(deprecationMessage); 154 } 155 } 156 // Retrieve optional target directory if defined 157 String outputDirectoryStr = config.getProperty(templateName + ".target"); 158 File outputDirectory = (outputDirectoryStr != null) ? new File(generator.getNuxeoHome(), 159 outputDirectoryStr) : getOutputDirectory(); 160 for (File in : listFiles) { 161 // copy template(s) directories parsing properties 162 newFilesList.addAll(templateParser.processDirectory(in, new File(outputDirectory, in.getName()))); 163 } 164 } 165 } 166 storeNewFilesList(newFilesList); 167 } 168 169 /** 170 * Delete files previously deployed by templates. If a file had been overwritten by a template, it will be restored. 171 * Helps the server returning to the state before any template was applied. 172 */ 173 private void deleteTemplateFiles() throws IOException, ConfigurationException { 174 File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); 175 if (!newFiles.exists()) { 176 return; 177 } 178 BufferedReader reader = null; 179 try { 180 reader = new BufferedReader(new FileReader(newFiles)); 181 String line; 182 while ((line = reader.readLine()) != null) { 183 if (line.endsWith(".bak")) { 184 log.debug("Restore " + line); 185 try { 186 File backup = new File(generator.getNuxeoHome(), line); 187 File original = new File(generator.getNuxeoHome(), line.substring(0, line.length() - 4)); 188 FileUtils.copyFile(backup, original); 189 backup.delete(); 190 } catch (IOException e) { 191 throw new ConfigurationException(String.format("Failed to restore %s from %s\nEdit or " 192 + "delete %s to bypass that error.", line.substring(0, line.length() - 4), line, 193 newFiles), e); 194 } 195 } else { 196 log.debug("Remove " + line); 197 new File(generator.getNuxeoHome(), line).delete(); 198 } 199 } 200 } finally { 201 IOUtils.closeQuietly(reader); 202 } 203 newFiles.delete(); 204 } 205 206 /** 207 * Store into {@link #NEW_FILES} the list of new files deployed by the templates. For later use by 208 * {@link #deleteTemplateFiles()} 209 */ 210 private void storeNewFilesList(List<String> newFilesList) throws IOException { 211 BufferedWriter writer = null; 212 try { 213 // Store new files listing 214 File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); 215 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFiles, false),"UTF-8")); 216 int index = generator.getNuxeoHome().getCanonicalPath().length() + 1; 217 for (String filepath : newFilesList) { 218 writer.write(new File(filepath).getCanonicalPath().substring(index)); 219 writer.newLine(); 220 } 221 } finally { 222 IOUtils.closeQuietly(writer); 223 } 224 } 225 226 /** 227 * @return output directory for files generation 228 */ 229 protected File getOutputDirectory() { 230 return getRuntimeHome(); 231 } 232 233 /** 234 * @return Default data directory path relative to Nuxeo Home 235 * @since 5.4.2 236 */ 237 protected String getDefaultDataDir() { 238 return org.nuxeo.common.Environment.DEFAULT_DATA_DIR; 239 } 240 241 /** 242 * Returns the Home of NuxeoRuntime (same as Framework.getRuntime().getHome().getAbsolutePath()) 243 */ 244 protected abstract File getRuntimeHome(); 245 246 /** 247 * @return Data directory 248 * @since 5.4.2 249 */ 250 public File getDataDir() { 251 if (dataDir == null) { 252 dataDir = new File(generator.getNuxeoHome(), getDefaultDataDir()); 253 } 254 return dataDir; 255 } 256 257 /** 258 * @return Log directory 259 * @since 5.4.2 260 */ 261 public File getLogDir() { 262 if (logDir == null) { 263 logDir = new File(generator.getNuxeoHome(), org.nuxeo.common.Environment.DEFAULT_LOG_DIR); 264 } 265 return logDir; 266 } 267 268 /** 269 * @param dataDirStr Data directory path to set 270 * @since 5.4.2 271 */ 272 public void setDataDir(String dataDirStr) { 273 dataDir = new File(dataDirStr); 274 dataDir.mkdirs(); 275 } 276 277 /** 278 * @param logDirStr Log directory path to set 279 * @since 5.4.2 280 */ 281 public void setLogDir(String logDirStr) { 282 logDir = new File(logDirStr); 283 logDir.mkdirs(); 284 } 285 286 /** 287 * Initialize logs. This is called before {@link ConfigurationGenerator#init()} so the {@code logDir} field is not 288 * yet initialized 289 * 290 * @since 5.4.2 291 */ 292 public void initLogs() { 293 File logFile = getLogConfFile(); 294 try { 295 String logDirectory = System.getProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR); 296 if (logDirectory == null) { 297 System.setProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR, getLogDir().getPath()); 298 } 299 if (logFile == null || !logFile.exists()) { 300 System.out.println("No logs configuration, will setup a basic one."); 301 BasicConfigurator.configure(); 302 } else { 303 System.out.println("Try to configure logs with " + logFile); 304 DOMConfigurator.configure(logFile.toURI().toURL()); 305 } 306 log.info("Logs successfully configured."); 307 } catch (MalformedURLException e) { 308 log.error("Could not initialize logs with " + logFile, e); 309 } 310 } 311 312 /** 313 * @return Pid directory (usually known as "run directory"); Returns log directory if not set by configuration. 314 * @since 5.4.2 315 */ 316 public File getPidDir() { 317 if (pidDir == null) { 318 pidDir = getLogDir(); 319 } 320 return pidDir; 321 } 322 323 /** 324 * @param pidDirStr Pid directory path to set 325 * @since 5.4.2 326 */ 327 public void setPidDir(String pidDirStr) { 328 pidDir = new File(pidDirStr); 329 pidDir.mkdirs(); 330 } 331 332 /** 333 * Check server paths; warn if existing deprecated paths. Override this method to perform server specific checks. 334 * 335 * @throws ConfigurationException If deprecated paths have been detected 336 * @since 5.4.2 337 */ 338 public void checkPaths() throws ConfigurationException { 339 File badInstanceClid = new File(generator.getNuxeoHome(), getDefaultDataDir() + File.separator 340 + "instance.clid"); 341 if (badInstanceClid.exists() && !getDataDir().equals(badInstanceClid.getParentFile())) { 342 log.warn(String.format("Moving %s to %s.", badInstanceClid, getDataDir())); 343 try { 344 FileUtils.moveFileToDirectory(badInstanceClid, getDataDir(), true); 345 } catch (IOException e) { 346 throw new ConfigurationException("NXP-6722 move failed: " + e.getMessage(), e); 347 } 348 } 349 350 File oldPackagesPath = new File(getDataDir(), getDefaultPackagesDir()); 351 if (oldPackagesPath.exists() && !oldPackagesPath.equals(getPackagesDir())) { 352 log.warn(String.format( 353 "NXP-8014 Packages cache location changed. You can safely delete %s or move its content to %s", 354 oldPackagesPath, getPackagesDir())); 355 } 356 357 } 358 359 /** 360 * @return Temporary directory 361 * @since 5.4.2 362 */ 363 public File getTmpDir() { 364 if (tmpDir == null) { 365 tmpDir = new File(generator.getNuxeoHome(), getDefaultTmpDir()); 366 } 367 return tmpDir; 368 } 369 370 /** 371 * @return Default temporary directory path relative to Nuxeo Home 372 * @since 5.4.2 373 */ 374 public String getDefaultTmpDir() { 375 return org.nuxeo.common.Environment.DEFAULT_TMP_DIR; 376 } 377 378 /** 379 * @param tmpDirStr Temporary directory path to set 380 * @since 5.4.2 381 */ 382 public void setTmpDir(String tmpDirStr) { 383 tmpDir = new File(tmpDirStr); 384 tmpDir.mkdirs(); 385 } 386 387 /** 388 * @see Environment 389 * @param key directory system key 390 * @param directory absolute or relative directory path 391 * @since 5.4.2 392 */ 393 public void setDirectory(String key, String directory) { 394 String absoluteDirectory = setAbsolutePath(key, directory); 395 if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { 396 setDataDir(absoluteDirectory); 397 } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { 398 setLogDir(absoluteDirectory); 399 } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { 400 setPidDir(absoluteDirectory); 401 } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { 402 setTmpDir(absoluteDirectory); 403 } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { 404 setPackagesDir(absoluteDirectory); 405 } else { 406 log.error("Unknown directory key: " + key); 407 } 408 } 409 410 /** 411 * @since 5.9.4 412 */ 413 private void setPackagesDir(String packagesDirStr) { 414 packagesDir = new File(packagesDirStr); 415 packagesDir.mkdirs(); 416 } 417 418 /** 419 * Make absolute the directory passed in parameter. If it was relative, then store absolute path in user config 420 * instead of relative and return value 421 * 422 * @param key Directory system key 423 * @param directory absolute or relative directory path 424 * @return absolute directory path 425 * @since 5.4.2 426 */ 427 private String setAbsolutePath(String key, String directory) { 428 if (!new File(directory).isAbsolute()) { 429 directory = new File(generator.getNuxeoHome(), directory).getPath(); 430 generator.getUserConfig().setProperty(key, directory); 431 } 432 return directory; 433 } 434 435 /** 436 * @see Environment 437 * @param key directory system key 438 * @return Directory denoted by key 439 * @since 5.4.2 440 */ 441 public File getDirectory(String key) { 442 if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { 443 return getDataDir(); 444 } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { 445 return getLogDir(); 446 } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { 447 return getPidDir(); 448 } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { 449 return getTmpDir(); 450 } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { 451 return getPackagesDir(); 452 } else { 453 log.error("Unknown directory key: " + key); 454 return null; 455 } 456 } 457 458 /** 459 * Check if oldPath exist; if so, then raise a ConfigurationException with information for fixing issue 460 * 461 * @param oldPath Path that must NOT exist 462 * @param message Error message thrown with exception 463 * @throws ConfigurationException If an old path has been discovered 464 */ 465 protected void checkPath(File oldPath, String message) throws ConfigurationException { 466 if (oldPath.exists()) { 467 log.error("Deprecated paths used."); 468 throw new ConfigurationException(message); 469 } 470 } 471 472 /** 473 * @return Log4J configuration file 474 * @since 5.4.2 475 */ 476 public abstract File getLogConfFile(); 477 478 /** 479 * @return Nuxeo config directory 480 * @since 5.4.2 481 */ 482 public abstract File getConfigDir(); 483 484 /** 485 * @since 5.4.2 486 */ 487 public void prepareWizardStart() { 488 // Nothing to do by default 489 } 490 491 /** 492 * @since 5.4.2 493 */ 494 public void cleanupPostWizard() { 495 // Nothing to do by default 496 } 497 498 /** 499 * Override it to make the wizard available for a given server. 500 * 501 * @return true if configuration wizard is required before starting Nuxeo 502 * @since 5.4.2 503 * @see #prepareWizardStart() 504 * @see #cleanupPostWizard() 505 */ 506 public boolean isWizardAvailable() { 507 return false; 508 } 509 510 /** 511 * @param userConfig Properties to dump into config directory 512 * @since 5.4.2 513 */ 514 public void dumpProperties(CryptoProperties userConfig) { 515 Properties dumpedProperties = filterSystemProperties(userConfig); 516 File dumpedFile = generator.getDumpedConfig(); 517 OutputStreamWriter os = null; 518 try { 519 os = new OutputStreamWriter(new FileOutputStream(dumpedFile, false), "UTF-8"); 520 dumpedProperties.store(os, "Generated by " + getClass()); 521 } catch (FileNotFoundException e) { 522 log.error(e); 523 } catch (IOException e) { 524 log.error("Could not dump properties to " + dumpedFile, e); 525 } finally { 526 IOUtils.closeQuietly(os); 527 } 528 } 529 530 /** 531 * Extract Nuxeo properties from given Properties (System properties are removed, except those set by Nuxeo) 532 * 533 * @param properties Properties to be filtered 534 * @return copy of given properties filtered out of System properties 535 * @since 5.4.2 536 */ 537 public Properties filterSystemProperties(CryptoProperties properties) { 538 Properties dumpedProperties = new Properties(); 539 for (@SuppressWarnings("unchecked") 540 Enumeration<String> propertyNames = (Enumeration<String>) properties.propertyNames(); propertyNames.hasMoreElements();) { 541 String key = propertyNames.nextElement(); 542 // Exclude System properties except Nuxeo's System properties 543 if (!System.getProperties().containsKey(key) || NUXEO_SYSTEM_PROPERTIES.contains(key)) { 544 dumpedProperties.setProperty(key, properties.getRawProperty(key)); 545 } 546 } 547 return dumpedProperties; 548 } 549 550 /** 551 * @return Nuxeo's third party libraries directory 552 * @since 5.4.1 553 */ 554 public File getNuxeoLibDir() { 555 return new File(getRuntimeHome(), "lib"); 556 } 557 558 /** 559 * @return Server's third party libraries directory 560 * @since 5.4.1 561 */ 562 public abstract File getServerLibDir(); 563 564 /** 565 * @since 5.7 566 */ 567 public void verifyInstallation() throws ConfigurationException { 568 checkPaths(); 569 checkNetwork(); 570 } 571 572 /** 573 * Perform server specific checks, not already done by {@link ConfigurationGenerator#checkAddressesAndPorts()} 574 * 575 * @since 5.7 576 * @see ConfigurationGenerator#checkAddressesAndPorts() 577 */ 578 protected void checkNetwork() throws ConfigurationException { 579 } 580 581 /** 582 * Override to add server specific parameters to the list of parameters to migrate 583 * 584 * @since 5.7 585 */ 586 protected void addServerSpecificParameters(Map<String, String> parametersmigration) { 587 // Nothing to do 588 } 589 590 /** 591 * @return Marketplace Packages directory 592 * @since 5.9.4 593 */ 594 public File getPackagesDir() { 595 if (packagesDir == null) { 596 packagesDir = new File(generator.getNuxeoHome(), getDefaultPackagesDir()); 597 } 598 return packagesDir; 599 } 600 601 /** 602 * @return Default MP directory path relative to Nuxeo Home 603 * @since 5.9.4 604 */ 605 public String getDefaultPackagesDir() { 606 return org.nuxeo.common.Environment.DEFAULT_MP_DIR; 607 } 608 609 /** 610 * Introspect the server and builds the instance info 611 * 612 * @since 8.3 613 */ 614 public InstanceInfo getInfo(String clid, List<LocalPackage> pkgs) { 615 InstanceInfo nxInstance = new InstanceInfo(); 616 nxInstance.NUXEO_CONF = generator.getNuxeoConf().getPath(); 617 nxInstance.NUXEO_HOME = generator.getNuxeoHome().getPath(); 618 // distribution 619 File distFile = new File(generator.getConfigDir(), "distribution.properties"); 620 if (!distFile.exists()) { 621 // fallback in the file in templates 622 distFile = new File(generator.getNuxeoHome(), "templates"); 623 distFile = new File(distFile, "common"); 624 distFile = new File(distFile, "config"); 625 distFile = new File(distFile, "distribution.properties"); 626 } 627 try { 628 nxInstance.distribution = new DistributionInfo(distFile); 629 } catch (IOException e) { 630 nxInstance.distribution = new DistributionInfo(); 631 } 632 // packages 633 nxInstance.clid = clid; 634 Set<String> pkgTemplates = new HashSet<>(); 635 for (LocalPackage pkg : pkgs) { 636 final PackageInfo info = new PackageInfo(pkg); 637 nxInstance.packages.add(info); 638 pkgTemplates.addAll(info.templates); 639 } 640 // templates 641 nxInstance.config = new ConfigurationInfo(); 642 nxInstance.config.dbtemplate = generator.extractDatabaseTemplateName(); 643 String userTemplates = generator.getUserTemplates(); 644 StringTokenizer st = new StringTokenizer(userTemplates, ","); 645 while (st.hasMoreTokens()) { 646 String template = st.nextToken(); 647 if (template.equals(nxInstance.config.dbtemplate)) { 648 continue; 649 } 650 if (pkgTemplates.contains(template)) { 651 nxInstance.config.pkgtemplates.add(template); 652 } else { 653 File testBase = new File(generator.getNuxeoHome(), ConfigurationGenerator.TEMPLATES 654 + File.separator + template); 655 if (testBase.exists()) { 656 nxInstance.config.basetemplates.add(template); 657 } else { 658 nxInstance.config.usertemplates.add(template); 659 } 660 } 661 } 662 // Settings from nuxeo.conf 663 CryptoProperties userConfig = generator.getUserConfig(); 664 for (Object item : new TreeSet<>(userConfig.keySet())) { 665 String key = (String) item; 666 String value = userConfig.getRawProperty(key); 667 if (JAVA_OPTS.equals(key)) { 668 value = generator.getJavaOptsString(); 669 } 670 if (ConfigurationGenerator.SECRET_KEYS.contains(key) || key.contains("password") 671 || key.equals(Environment.SERVER_STATUS_KEY) || Crypto.isEncrypted(value)) { 672 value = "********"; 673 } 674 nxInstance.config.keyvals.add(new KeyValueInfo(key, value)); 675 } 676 return nxInstance; 677 } 678}