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