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