001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.protocols; 028 029import java.io.File; 030import java.io.IOException; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.LinkedHashMap; 034import java.util.List; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.LocalizableMessageBuilder; 038import org.opends.server.admin.server.ConfigurationChangeListener; 039import org.opends.server.admin.std.server.ConnectionHandlerCfg; 040import org.opends.server.admin.std.server.LDIFConnectionHandlerCfg; 041import org.opends.server.api.AlertGenerator; 042import org.opends.server.api.ClientConnection; 043import org.opends.server.api.ConnectionHandler; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.core.ServerContext; 046import org.forgerock.i18n.slf4j.LocalizedLogger; 047import org.opends.server.protocols.internal.InternalClientConnection; 048import org.forgerock.opendj.config.server.ConfigChangeResult; 049import org.opends.server.types.DirectoryConfig; 050import org.opends.server.types.DN; 051import org.opends.server.types.ExistingFileBehavior; 052import org.opends.server.types.HostPort; 053import org.opends.server.types.LDIFExportConfig; 054import org.opends.server.types.LDIFImportConfig; 055import org.opends.server.types.Operation; 056import org.opends.server.util.AddChangeRecordEntry; 057import org.opends.server.util.ChangeRecordEntry; 058import org.opends.server.util.DeleteChangeRecordEntry; 059import org.opends.server.util.LDIFException; 060import org.opends.server.util.LDIFReader; 061import org.opends.server.util.LDIFWriter; 062import org.opends.server.util.ModifyChangeRecordEntry; 063import org.opends.server.util.ModifyDNChangeRecordEntry; 064import org.opends.server.util.TimeThread; 065 066import static org.opends.messages.ProtocolMessages.*; 067import static org.opends.server.util.ServerConstants.*; 068import static org.opends.server.util.StaticUtils.*; 069 070/** 071 * This class defines an LDIF connection handler, which can be used to watch for 072 * new LDIF files to be placed in a specified directory. If a new LDIF file is 073 * detected, the connection handler will process any changes contained in that 074 * file as internal operations. 075 */ 076public final class LDIFConnectionHandler 077 extends ConnectionHandler<LDIFConnectionHandlerCfg> 078 implements ConfigurationChangeListener<LDIFConnectionHandlerCfg>, 079 AlertGenerator 080{ 081 /** The debug log tracer for this class. */ 082 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 083 084 085 086 /** Indicates whether this connection handler is currently stopped. */ 087 private volatile boolean isStopped; 088 089 /** Indicates whether we should stop this connection handler. */ 090 private volatile boolean stopRequested; 091 092 /** The path to the directory to watch for new LDIF files. */ 093 private File ldifDirectory; 094 095 /** The internal client connection that will be used for all processing. */ 096 private InternalClientConnection conn; 097 098 /** The current configuration for this LDIF connection handler. */ 099 private LDIFConnectionHandlerCfg currentConfig; 100 101 /** The thread used to run the connection handler. */ 102 private Thread connectionHandlerThread; 103 104 /** Help to not warn permanently and fullfill the log file in debug mode. */ 105 private boolean alreadyWarn; 106 107 108 /** 109 * Creates a new instance of this connection handler. All initialization 110 * should be performed in the {@code initializeConnectionHandler} method. 111 */ 112 public LDIFConnectionHandler() 113 { 114 super("LDIFConnectionHandler"); 115 116 isStopped = true; 117 stopRequested = false; 118 connectionHandlerThread = null; 119 alreadyWarn = false; 120 } 121 122 123 124 /** {@inheritDoc} */ 125 @Override 126 public void initializeConnectionHandler(ServerContext serverContext, LDIFConnectionHandlerCfg 127 configuration) 128 { 129 String ldifDirectoryPath = configuration.getLDIFDirectory(); 130 ldifDirectory = new File(ldifDirectoryPath); 131 132 // If we have a relative path to the instance, get the absolute one. 133 if ( ! ldifDirectory.isAbsolute() ) { 134 ldifDirectory = new File(DirectoryServer.getInstanceRoot() 135 + File.separator + ldifDirectoryPath); 136 } 137 138 if (ldifDirectory.exists()) 139 { 140 if (! ldifDirectory.isDirectory()) 141 { 142 // The path specified as the LDIF directory exists, but isn't a 143 // directory. This is probably a mistake, and we should at least log 144 // a warning message. 145 logger.warn(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_NOT_DIRECTORY, 146 ldifDirectory.getAbsolutePath(), configuration.dn()); 147 } 148 } 149 else 150 { 151 // The path specified as the LDIF directory doesn't exist. We should log 152 // a warning message saying that we won't do anything until it's created. 153 logger.warn(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_MISSING, 154 ldifDirectory.getAbsolutePath(), configuration.dn()); 155 } 156 157 this.currentConfig = configuration; 158 currentConfig.addLDIFChangeListener(this); 159 DirectoryConfig.registerAlertGenerator(this); 160 conn = InternalClientConnection.getRootConnection(); 161 } 162 163 164 165 /** {@inheritDoc} */ 166 @Override 167 public void finalizeConnectionHandler(LocalizableMessage finalizeReason) 168 { 169 stopRequested = true; 170 171 for (int i=0; i < 5; i++) 172 { 173 if (isStopped) 174 { 175 return; 176 } 177 else 178 { 179 try 180 { 181 if (connectionHandlerThread != null && connectionHandlerThread.isAlive()) 182 { 183 connectionHandlerThread.join(100); 184 connectionHandlerThread.interrupt(); 185 } 186 else 187 { 188 return; 189 } 190 } catch (Exception e) {} 191 } 192 } 193 } 194 195 196 197 /** {@inheritDoc} */ 198 @Override 199 public String getConnectionHandlerName() 200 { 201 return "LDIF Connection Handler"; 202 } 203 204 205 206 /** {@inheritDoc} */ 207 @Override 208 public String getProtocol() 209 { 210 return "LDIF"; 211 } 212 213 214 215 /** {@inheritDoc} */ 216 @Override 217 public Collection<HostPort> getListeners() 218 { 219 // There are no listeners for this connection handler. 220 return Collections.<HostPort>emptySet(); 221 } 222 223 224 225 /** {@inheritDoc} */ 226 @Override 227 public Collection<ClientConnection> getClientConnections() 228 { 229 // There are no client connections for this connection handler. 230 return Collections.<ClientConnection>emptySet(); 231 } 232 233 234 235 /** {@inheritDoc} */ 236 @Override 237 public void run() 238 { 239 isStopped = false; 240 connectionHandlerThread = Thread.currentThread(); 241 242 try 243 { 244 while (! stopRequested) 245 { 246 try 247 { 248 long startTime = System.currentTimeMillis(); 249 250 File dir = ldifDirectory; 251 if (dir.exists() && dir.isDirectory()) 252 { 253 File[] ldifFiles = dir.listFiles(); 254 if (ldifFiles != null) 255 { 256 for (File f : ldifFiles) 257 { 258 if (f.getName().endsWith(".ldif")) 259 { 260 processLDIFFile(f); 261 } 262 } 263 } 264 } 265 else 266 { 267 if (!alreadyWarn && logger.isTraceEnabled()) 268 { 269 logger.trace("LDIF connection handler directory " + 270 dir.getAbsolutePath() + 271 " doesn't exist or isn't a directory"); 272 alreadyWarn = true; 273 } 274 } 275 276 if (! stopRequested) 277 { 278 long currentTime = System.currentTimeMillis(); 279 long sleepTime = startTime + currentConfig.getPollInterval() - 280 currentTime; 281 if (sleepTime > 0) 282 { 283 try 284 { 285 Thread.sleep(sleepTime); 286 } 287 catch (InterruptedException ie) 288 { 289 logger.traceException(ie); 290 } 291 } 292 } 293 } 294 catch (Exception e) 295 { 296 logger.traceException(e); 297 } 298 } 299 } 300 finally 301 { 302 connectionHandlerThread = null; 303 isStopped = true; 304 } 305 } 306 307 308 309 /** 310 * Processes the contents of the provided LDIF file. 311 * 312 * @param ldifFile The LDIF file to be processed. 313 */ 314 private void processLDIFFile(File ldifFile) 315 { 316 if (logger.isTraceEnabled()) 317 { 318 logger.trace("Beginning processing on LDIF file " + 319 ldifFile.getAbsolutePath()); 320 } 321 322 boolean fullyProcessed = false; 323 boolean errorEncountered = false; 324 String inputPath = ldifFile.getAbsolutePath(); 325 326 LDIFImportConfig importConfig = 327 new LDIFImportConfig(inputPath); 328 importConfig.setInvokeImportPlugins(false); 329 importConfig.setValidateSchema(true); 330 331 String outputPath = inputPath + ".applied." + TimeThread.getGMTTime(); 332 if (new File(outputPath).exists()) 333 { 334 int i=2; 335 while (true) 336 { 337 if (! new File(outputPath + "." + i).exists()) 338 { 339 outputPath = outputPath + "." + i; 340 break; 341 } 342 343 i++; 344 } 345 } 346 347 LDIFExportConfig exportConfig = 348 new LDIFExportConfig(outputPath, ExistingFileBehavior.APPEND); 349 if (logger.isTraceEnabled()) 350 { 351 logger.trace("Creating applied file " + outputPath); 352 } 353 354 355 LDIFReader reader = null; 356 LDIFWriter writer = null; 357 358 try 359 { 360 reader = new LDIFReader(importConfig); 361 writer = new LDIFWriter(exportConfig); 362 363 while (true) 364 { 365 ChangeRecordEntry changeRecord; 366 try 367 { 368 changeRecord = reader.readChangeRecord(false); 369 if (logger.isTraceEnabled()) 370 { 371 logger.trace("Read change record entry %s", changeRecord); 372 } 373 } 374 catch (LDIFException le) 375 { 376 logger.traceException(le); 377 378 errorEncountered = true; 379 if (le.canContinueReading()) 380 { 381 LocalizableMessage m = 382 ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_NONFATAL.get( 383 le.getMessageObject()); 384 writer.writeComment(m, 78); 385 continue; 386 } 387 else 388 { 389 LocalizableMessage m = 390 ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_FATAL.get( 391 le.getMessageObject()); 392 writer.writeComment(m, 78); 393 DirectoryConfig.sendAlertNotification(this, 394 ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m); 395 break; 396 } 397 } 398 399 Operation operation = null; 400 if (changeRecord == null) 401 { 402 fullyProcessed = true; 403 break; 404 } 405 406 if (changeRecord instanceof AddChangeRecordEntry) 407 { 408 operation = conn.processAdd((AddChangeRecordEntry) changeRecord); 409 } 410 else if (changeRecord instanceof DeleteChangeRecordEntry) 411 { 412 operation = conn.processDelete( 413 (DeleteChangeRecordEntry) changeRecord); 414 } 415 else if (changeRecord instanceof ModifyChangeRecordEntry) 416 { 417 operation = conn.processModify( 418 (ModifyChangeRecordEntry) changeRecord); 419 } 420 else if (changeRecord instanceof ModifyDNChangeRecordEntry) 421 { 422 operation = conn.processModifyDN( 423 (ModifyDNChangeRecordEntry) changeRecord); 424 } 425 426 if (operation == null) 427 { 428 LocalizableMessage m = INFO_LDIF_CONNHANDLER_UNKNOWN_CHANGETYPE.get( 429 changeRecord.getChangeOperationType().getLDIFChangeType()); 430 writer.writeComment(m, 78); 431 } 432 else 433 { 434 if (logger.isTraceEnabled()) 435 { 436 logger.trace("Result Code: %s", operation.getResultCode()); 437 } 438 439 LocalizableMessage m = INFO_LDIF_CONNHANDLER_RESULT_CODE.get( 440 operation.getResultCode().intValue(), 441 operation.getResultCode()); 442 writer.writeComment(m, 78); 443 444 LocalizableMessageBuilder errorMessage = operation.getErrorMessage(); 445 if (errorMessage != null && errorMessage.length() > 0) 446 { 447 m = INFO_LDIF_CONNHANDLER_ERROR_MESSAGE.get(errorMessage); 448 writer.writeComment(m, 78); 449 } 450 451 DN matchedDN = operation.getMatchedDN(); 452 if (matchedDN != null) 453 { 454 m = INFO_LDIF_CONNHANDLER_MATCHED_DN.get(matchedDN); 455 writer.writeComment(m, 78); 456 } 457 458 List<String> referralURLs = operation.getReferralURLs(); 459 if (referralURLs != null && !referralURLs.isEmpty()) 460 { 461 for (String url : referralURLs) 462 { 463 m = INFO_LDIF_CONNHANDLER_REFERRAL_URL.get(url); 464 writer.writeComment(m, 78); 465 } 466 } 467 } 468 469 writer.writeChangeRecord(changeRecord); 470 } 471 } 472 catch (IOException ioe) 473 { 474 logger.traceException(ioe); 475 476 fullyProcessed = false; 477 LocalizableMessage m = ERR_LDIF_CONNHANDLER_IO_ERROR.get(inputPath, 478 getExceptionMessage(ioe)); 479 logger.error(m); 480 DirectoryConfig.sendAlertNotification(this, 481 ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m); 482 } 483 finally 484 { 485 close(reader, writer); 486 } 487 488 if (errorEncountered || !fullyProcessed) 489 { 490 String renamedPath = inputPath + ".errors-encountered." + 491 TimeThread.getGMTTime(); 492 if (new File(renamedPath).exists()) 493 { 494 int i=2; 495 while (true) 496 { 497 if (! new File(renamedPath + "." + i).exists()) 498 { 499 renamedPath = renamedPath + "." + i; 500 } 501 502 i++; 503 } 504 } 505 506 try 507 { 508 if (logger.isTraceEnabled()) 509 { 510 logger.trace("Renaming source file to " + renamedPath); 511 } 512 513 ldifFile.renameTo(new File(renamedPath)); 514 } 515 catch (Exception e) 516 { 517 logger.traceException(e); 518 519 LocalizableMessage m = ERR_LDIF_CONNHANDLER_CANNOT_RENAME.get(inputPath, 520 renamedPath, getExceptionMessage(e)); 521 logger.error(m); 522 DirectoryConfig.sendAlertNotification(this, 523 ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m); 524 } 525 } 526 else 527 { 528 try 529 { 530 if (logger.isTraceEnabled()) 531 { 532 logger.trace("Deleting source file"); 533 } 534 535 ldifFile.delete(); 536 } 537 catch (Exception e) 538 { 539 logger.traceException(e); 540 541 LocalizableMessage m = ERR_LDIF_CONNHANDLER_CANNOT_DELETE.get(inputPath, 542 getExceptionMessage(e)); 543 logger.error(m); 544 DirectoryConfig.sendAlertNotification(this, 545 ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m); 546 } 547 } 548 } 549 550 551 552 /** {@inheritDoc} */ 553 @Override 554 public void toString(StringBuilder buffer) 555 { 556 buffer.append("LDIFConnectionHandler(ldifDirectory=\""); 557 buffer.append(ldifDirectory.getAbsolutePath()); 558 buffer.append("\", pollInterval="); 559 buffer.append(currentConfig.getPollInterval()); 560 buffer.append("ms)"); 561 } 562 563 564 565 /** {@inheritDoc} */ 566 @Override 567 public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, 568 List<LocalizableMessage> unacceptableReasons) 569 { 570 LDIFConnectionHandlerCfg cfg = (LDIFConnectionHandlerCfg) configuration; 571 return isConfigurationChangeAcceptable(cfg, unacceptableReasons); 572 } 573 574 575 576 /** {@inheritDoc} */ 577 public boolean isConfigurationChangeAcceptable( 578 LDIFConnectionHandlerCfg configuration, 579 List<LocalizableMessage> unacceptableReasons) 580 { 581 // The configuration should always be acceptable. 582 return true; 583 } 584 585 586 587 /** {@inheritDoc} */ 588 public ConfigChangeResult applyConfigurationChange( 589 LDIFConnectionHandlerCfg configuration) 590 { 591 // The only processing we need to do here is to get the LDIF directory and 592 // create a File object from it. 593 File newLDIFDirectory = new File(configuration.getLDIFDirectory()); 594 this.ldifDirectory = newLDIFDirectory; 595 currentConfig = configuration; 596 return new ConfigChangeResult(); 597 } 598 599 600 601 /** {@inheritDoc} */ 602 @Override 603 public DN getComponentEntryDN() 604 { 605 return currentConfig.dn(); 606 } 607 608 609 610 /** {@inheritDoc} */ 611 public String getClassName() 612 { 613 return LDIFConnectionHandler.class.getName(); 614 } 615 616 617 618 /** {@inheritDoc} */ 619 public LinkedHashMap<String,String> getAlerts() 620 { 621 LinkedHashMap<String,String> alerts = new LinkedHashMap<>(); 622 623 alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, 624 ALERT_DESCRIPTION_LDIF_CONNHANDLER_PARSE_ERROR); 625 alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, 626 ALERT_DESCRIPTION_LDIF_CONNHANDLER_IO_ERROR); 627 628 return alerts; 629 } 630} 631