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.FileNotFoundException; 025import java.io.FileOutputStream; 026import java.io.FileReader; 027import java.io.FileWriter; 028import java.io.FilenameFilter; 029import java.io.IOException; 030import java.io.OutputStream; 031import java.net.MalformedURLException; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Enumeration; 035import java.util.List; 036import java.util.Map; 037import java.util.Properties; 038 039import org.apache.commons.io.FileUtils; 040import org.apache.commons.io.IOUtils; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.apache.log4j.BasicConfigurator; 044import org.apache.log4j.xml.DOMConfigurator; 045 046import org.nuxeo.common.codec.CryptoProperties; 047import org.nuxeo.common.utils.TextTemplate; 048 049import freemarker.template.TemplateException; 050 051/** 052 * @author jcarsique 053 */ 054public abstract class ServerConfigurator { 055 056 protected static final Log log = LogFactory.getLog(ServerConfigurator.class); 057 058 protected final ConfigurationGenerator generator; 059 060 protected File dataDir = null; 061 062 protected File logDir = null; 063 064 protected File pidDir = null; 065 066 protected File libDir = null; 067 068 protected File tmpDir = null; 069 070 protected File packagesDir = null; 071 072 /** 073 * @since 5.4.2 074 */ 075 public static final List<String> NUXEO_SYSTEM_PROPERTIES = Arrays.asList(new String[] { "nuxeo.conf", "nuxeo.home", 076 "log.id" }); 077 078 protected static final String DEFAULT_CONTEXT_NAME = "/nuxeo"; 079 080 private static final String NEW_FILES = ConfigurationGenerator.TEMPLATES + File.separator + "files.list"; 081 082 /** 083 * @since 5.4.2 084 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_LOG_DIR} instead. 085 */ 086 @Deprecated 087 public static final String DEFAULT_LOG_DIR = org.nuxeo.common.Environment.DEFAULT_LOG_DIR; 088 089 /** 090 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_DATA_DIR} instead. 091 */ 092 @Deprecated 093 public static final String DEFAULT_DATA_DIR = org.nuxeo.common.Environment.DEFAULT_DATA_DIR; 094 095 /** 096 * @since 5.4.2 097 * @deprecated Since 5.9.4. Use {@link org.nuxeo.common.Environment#DEFAULT_TMP_DIR} instead. 098 */ 099 @Deprecated 100 public static final String DEFAULT_TMP_DIR = org.nuxeo.common.Environment.DEFAULT_TMP_DIR; 101 102 public ServerConfigurator(ConfigurationGenerator configurationGenerator) { 103 generator = configurationGenerator; 104 } 105 106 /** 107 * @return true if server configuration files already exist 108 */ 109 abstract boolean isConfigured(); 110 111 /** 112 * Generate configuration files from templates and given configuration parameters 113 * 114 * @param config Properties with configuration parameters for template replacement 115 * @throws ConfigurationException 116 */ 117 protected void parseAndCopy(Properties config) throws IOException, TemplateException, ConfigurationException { 118 // FilenameFilter for excluding "nuxeo.defaults" files from copy 119 final FilenameFilter filter = new FilenameFilter() { 120 @Override 121 public boolean accept(File dir, String name) { 122 return !ConfigurationGenerator.NUXEO_DEFAULT_CONF.equals(name); 123 } 124 }; 125 final TextTemplate templateParser = new TextTemplate(config); 126 templateParser.setKeepEncryptedAsVar(true); 127 templateParser.setTrim(true); 128 templateParser.setTextParsingExtensions(config.getProperty( 129 ConfigurationGenerator.PARAM_TEMPLATES_PARSING_EXTENSIONS, "xml,properties,nx")); 130 templateParser.setFreemarkerParsingExtensions(config.getProperty( 131 ConfigurationGenerator.PARAM_TEMPLATES_FREEMARKER_EXTENSIONS, "nxftl")); 132 133 deleteTemplateFiles(); 134 // add included templates directories 135 List<String> newFilesList = new ArrayList<>(); 136 for (File includedTemplate : generator.getIncludedTemplates()) { 137 File[] listFiles = includedTemplate.listFiles(filter); 138 if (listFiles != null) { 139 String templateName = includedTemplate.getName(); 140 log.debug(String.format("Parsing %s... %s", templateName, Arrays.toString(listFiles))); 141 // Check for deprecation 142 Boolean isDeprecated = Boolean.valueOf(config.getProperty(templateName + ".deprecated")); 143 if (isDeprecated) { 144 log.warn("WARNING: Template " + templateName + " is deprecated."); 145 String deprecationMessage = config.getProperty(templateName + ".deprecation"); 146 if (deprecationMessage != null) { 147 log.warn(deprecationMessage); 148 } 149 } 150 // Retrieve optional target directory if defined 151 String outputDirectoryStr = config.getProperty(templateName + ".target"); 152 File outputDirectory = (outputDirectoryStr != null) ? new File(generator.getNuxeoHome(), 153 outputDirectoryStr) : getOutputDirectory(); 154 for (File in : listFiles) { 155 // copy template(s) directories parsing properties 156 newFilesList.addAll(templateParser.processDirectory(in, new File(outputDirectory, in.getName()))); 157 } 158 } 159 } 160 storeNewFilesList(newFilesList); 161 } 162 163 /** 164 * Delete files previously deployed by templates. If a file had been overwritten by a template, it will be restored. 165 * Helps the server returning to the state before any template was applied. 166 * 167 * @throws IOException 168 * @throws ConfigurationException 169 */ 170 private void deleteTemplateFiles() throws IOException, ConfigurationException { 171 File newFiles = new File(generator.getNuxeoHome(), NEW_FILES); 172 if (!newFiles.exists()) { 173 return; 174 } 175 BufferedReader reader = null; 176 try { 177 reader = new BufferedReader(new FileReader(newFiles)); 178 String line; 179 while ((line = reader.readLine()) != null) { 180 if (line.endsWith(".bak")) { 181 log.debug("Restore " + line); 182 try { 183 File backup = new File(generator.getNuxeoHome(), line); 184 File original = new File(generator.getNuxeoHome(), line.substring(0, line.length() - 4)); 185 FileUtils.copyFile(backup, original); 186 backup.delete(); 187 } catch (IOException e) { 188 throw new ConfigurationException(String.format("Failed to restore %s from %s\nEdit or " 189 + "delete %s to bypass that error.", line.substring(0, line.length() - 4), line, 190 newFiles), e); 191 } 192 } else { 193 log.debug("Remove " + line); 194 new File(generator.getNuxeoHome(), line).delete(); 195 } 196 } 197 } finally { 198 IOUtils.closeQuietly(reader); 199 } 200 newFiles.delete(); 201 } 202 203 /** 204 * Store into {@link #NEW_FILES} the list of new files deployed by the templates. For later use by 205 * {@link #deleteTemplateFiles()} 206 * 207 * @param newFilesList 208 * @throws IOException 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 FileWriter(newFiles, false)); 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 * @param absoluteDirectory 412 * @since 5.9.4 413 */ 414 private void setPackagesDir(String packagesDirStr) { 415 packagesDir = new File(packagesDirStr); 416 packagesDir.mkdirs(); 417 } 418 419 /** 420 * Make absolute the directory passed in parameter. If it was relative, then store absolute path in user config 421 * instead of relative and return value 422 * 423 * @param key Directory system key 424 * @param directory absolute or relative directory path 425 * @return absolute directory path 426 * @since 5.4.2 427 */ 428 private String setAbsolutePath(String key, String directory) { 429 if (!new File(directory).isAbsolute()) { 430 directory = new File(generator.getNuxeoHome(), directory).getPath(); 431 generator.getUserConfig().setProperty(key, directory); 432 } 433 return directory; 434 } 435 436 /** 437 * @see Environment 438 * @param key directory system key 439 * @return Directory denoted by key 440 * @since 5.4.2 441 */ 442 public File getDirectory(String key) { 443 if (org.nuxeo.common.Environment.NUXEO_DATA_DIR.equals(key)) { 444 return getDataDir(); 445 } else if (org.nuxeo.common.Environment.NUXEO_LOG_DIR.equals(key)) { 446 return getLogDir(); 447 } else if (org.nuxeo.common.Environment.NUXEO_PID_DIR.equals(key)) { 448 return getPidDir(); 449 } else if (org.nuxeo.common.Environment.NUXEO_TMP_DIR.equals(key)) { 450 return getTmpDir(); 451 } else if (org.nuxeo.common.Environment.NUXEO_MP_DIR.equals(key)) { 452 return getPackagesDir(); 453 } else { 454 log.error("Unknown directory key: " + key); 455 return null; 456 } 457 } 458 459 /** 460 * Check if oldPath exist; if so, then raise a ConfigurationException with information for fixing issue 461 * 462 * @param oldPath Path that must NOT exist 463 * @param message Error message thrown with exception 464 * @throws ConfigurationException If an old path has been discovered 465 */ 466 protected void checkPath(File oldPath, String message) throws ConfigurationException { 467 if (oldPath.exists()) { 468 log.error("Deprecated paths used."); 469 throw new ConfigurationException(message); 470 } 471 } 472 473 /** 474 * @return Log4J configuration file 475 * @since 5.4.2 476 */ 477 public abstract File getLogConfFile(); 478 479 /** 480 * @return Nuxeo config directory 481 * @since 5.4.2 482 */ 483 public abstract File getConfigDir(); 484 485 /** 486 * @since 5.4.2 487 */ 488 public void prepareWizardStart() { 489 // Nothing to do by default 490 } 491 492 /** 493 * @since 5.4.2 494 */ 495 public void cleanupPostWizard() { 496 // Nothing to do by default 497 } 498 499 /** 500 * Override it to make the wizard available for a given server. 501 * 502 * @return true if configuration wizard is required before starting Nuxeo 503 * @since 5.4.2 504 * @see #prepareWizardStart() 505 * @see #cleanupPostWizard() 506 */ 507 public boolean isWizardAvailable() { 508 return false; 509 } 510 511 /** 512 * @param userConfig Properties to dump into config directory 513 * @since 5.4.2 514 */ 515 public void dumpProperties(CryptoProperties userConfig) { 516 Properties dumpedProperties = filterSystemProperties(userConfig); 517 File dumpedFile = generator.getDumpedConfig(); 518 OutputStream os = null; 519 try { 520 os = new FileOutputStream(dumpedFile, false); 521 dumpedProperties.store(os, "Generated by " + getClass()); 522 } catch (FileNotFoundException e) { 523 log.error(e); 524 } catch (IOException e) { 525 log.error("Could not dump properties to " + dumpedFile, e); 526 } finally { 527 IOUtils.closeQuietly(os); 528 } 529 } 530 531 /** 532 * Extract Nuxeo properties from given Properties (System properties are removed, except those set by Nuxeo) 533 * 534 * @param properties Properties to be filtered 535 * @return copy of given properties filtered out of System properties 536 * @since 5.4.2 537 */ 538 public Properties filterSystemProperties(CryptoProperties properties) { 539 Properties dumpedProperties = new Properties(); 540 for (@SuppressWarnings("unchecked") 541 Enumeration<String> propertyNames = (Enumeration<String>) properties.propertyNames(); propertyNames.hasMoreElements();) { 542 String key = propertyNames.nextElement(); 543 // Exclude System properties except Nuxeo's System properties 544 if (!System.getProperties().containsKey(key) || NUXEO_SYSTEM_PROPERTIES.contains(key)) { 545 dumpedProperties.setProperty(key, properties.getRawProperty(key)); 546 } 547 } 548 return dumpedProperties; 549 } 550 551 /** 552 * @return Nuxeo's third party libraries directory 553 * @since 5.4.1 554 */ 555 public File getNuxeoLibDir() { 556 return new File(getRuntimeHome(), "lib"); 557 } 558 559 /** 560 * @return Server's third party libraries directory 561 * @since 5.4.1 562 */ 563 public abstract File getServerLibDir(); 564 565 /** 566 * @throws ConfigurationException 567 * @since 5.7 568 */ 569 public void verifyInstallation() throws ConfigurationException { 570 checkPaths(); 571 checkNetwork(); 572 } 573 574 /** 575 * Perform server specific checks, not already done by {@link ConfigurationGenerator#checkAddressesAndPorts()} 576 * 577 * @throws ConfigurationException 578 * @since 5.7 579 * @see ConfigurationGenerator#checkAddressesAndPorts() 580 */ 581 protected void checkNetwork() throws ConfigurationException { 582 } 583 584 /** 585 * Override to add server specific parameters to the list of parameters to migrate 586 * 587 * @param parametersmigration 588 * @since 5.7 589 */ 590 protected void addServerSpecificParameters(Map<String, String> parametersmigration) { 591 // Nothing to do 592 } 593 594 /** 595 * @return Marketplace Packages directory 596 * @since 5.9.4 597 */ 598 public File getPackagesDir() { 599 if (packagesDir == null) { 600 packagesDir = new File(generator.getNuxeoHome(), getDefaultPackagesDir()); 601 } 602 return packagesDir; 603 } 604 605 /** 606 * @return Default MP directory path relative to Nuxeo Home 607 * @since 5.9.4 608 */ 609 public String getDefaultPackagesDir() { 610 return org.nuxeo.common.Environment.DEFAULT_MP_DIR; 611 } 612 613}