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