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