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