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