001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.flume.appender; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.DataInputStream; 022import java.io.DataOutputStream; 023import java.io.File; 024import java.nio.charset.Charset; 025import java.nio.charset.StandardCharsets; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.concurrent.Callable; 029import java.util.concurrent.ExecutorService; 030import java.util.concurrent.Executors; 031import java.util.concurrent.Future; 032import java.util.concurrent.TimeUnit; 033import java.util.concurrent.atomic.AtomicLong; 034import javax.crypto.Cipher; 035import javax.crypto.SecretKey; 036 037import com.sleepycat.je.Cursor; 038import com.sleepycat.je.CursorConfig; 039import com.sleepycat.je.Database; 040import com.sleepycat.je.DatabaseConfig; 041import com.sleepycat.je.DatabaseEntry; 042import com.sleepycat.je.Environment; 043import com.sleepycat.je.EnvironmentConfig; 044import com.sleepycat.je.LockConflictException; 045import com.sleepycat.je.LockMode; 046import com.sleepycat.je.OperationStatus; 047import com.sleepycat.je.StatsConfig; 048import com.sleepycat.je.Transaction; 049import org.apache.flume.Event; 050import org.apache.flume.event.SimpleEvent; 051import org.apache.logging.log4j.LoggingException; 052import org.apache.logging.log4j.core.appender.ManagerFactory; 053import org.apache.logging.log4j.core.config.Property; 054import org.apache.logging.log4j.core.config.plugins.util.PluginManager; 055import org.apache.logging.log4j.core.config.plugins.util.PluginType; 056import org.apache.logging.log4j.core.util.ExecutorServices; 057import org.apache.logging.log4j.core.util.FileUtils; 058import org.apache.logging.log4j.core.util.Log4jThread; 059import org.apache.logging.log4j.core.util.Log4jThreadFactory; 060import org.apache.logging.log4j.core.util.SecretKeyProvider; 061import org.apache.logging.log4j.util.Strings; 062 063/** 064 * Manager that persists data to Berkeley DB before passing it on to Flume. 065 */ 066public class FlumePersistentManager extends FlumeAvroManager { 067 068 /** Attribute name for the key provider. */ 069 public static final String KEY_PROVIDER = "keyProvider"; 070 071 private static final Charset UTF8 = StandardCharsets.UTF_8; 072 073 private static final String DEFAULT_DATA_DIR = ".log4j/flumeData"; 074 075 private static final long SHUTDOWN_WAIT_MILLIS = 60000; 076 077 private static final long LOCK_TIMEOUT_SLEEP_MILLIS = 500; 078 079 private static BDBManagerFactory factory = new BDBManagerFactory(); 080 081 private final Database database; 082 083 private final Environment environment; 084 085 private final WriterThread worker; 086 087 private final Gate gate = new Gate(); 088 089 private final SecretKey secretKey; 090 091 private final int lockTimeoutRetryCount; 092 093 private final ExecutorService threadPool; 094 095 private final AtomicLong dbCount = new AtomicLong(); 096 097 /** 098 * Constructor 099 * @param name The unique name of this manager. 100 * @param shortName Original name for the Manager. 101 * @param agents An array of Agents. 102 * @param batchSize The number of events to include in a batch. 103 * @param retries The number of times to retry connecting before giving up. 104 * @param connectionTimeout The amount of time to wait for a connection to be established. 105 * @param requestTimeout The amount of time to wair for a response to a request. 106 * @param delay The amount of time to wait between retries. 107 * @param database The database to write to. 108 * @param environment The database environment. 109 * @param secretKey The SecretKey to use for encryption. 110 * @param lockTimeoutRetryCount The number of times to retry a lock timeout. 111 */ 112 protected FlumePersistentManager(final String name, final String shortName, final Agent[] agents, 113 final int batchSize, final int retries, final int connectionTimeout, 114 final int requestTimeout, final int delay, final Database database, 115 final Environment environment, final SecretKey secretKey, 116 final int lockTimeoutRetryCount) { 117 super(name, shortName, agents, batchSize, delay, retries, connectionTimeout, requestTimeout); 118 this.database = database; 119 this.environment = environment; 120 dbCount.set(database.count()); 121 this.worker = new WriterThread(database, environment, this, gate, batchSize, secretKey, dbCount, 122 lockTimeoutRetryCount); 123 this.worker.start(); 124 this.secretKey = secretKey; 125 this.threadPool = Executors.newCachedThreadPool(Log4jThreadFactory.createDaemonThreadFactory("Flume")); 126 this.lockTimeoutRetryCount = lockTimeoutRetryCount; 127 } 128 129 130 /** 131 * Returns a FlumeAvroManager. 132 * @param name The name of the manager. 133 * @param agents The agents to use. 134 * @param properties Properties to pass to the Manager. 135 * @param batchSize The number of events to include in a batch. 136 * @param retries The number of times to retry connecting before giving up. 137 * @param connectionTimeout The amount of time to wait to establish a connection. 138 * @param requestTimeout The amount of time to wait for a response to a request. 139 * @param delayMillis Amount of time to delay before delivering a batch. 140 * @param lockTimeoutRetryCount The number of times to retry after a lock timeout. 141 * @param dataDir The location of the Berkeley database. 142 * @return A FlumeAvroManager. 143 */ 144 public static FlumePersistentManager getManager(final String name, final Agent[] agents, 145 final Property[] properties, int batchSize, final int retries, 146 final int connectionTimeout, final int requestTimeout, 147 final int delayMillis, final int lockTimeoutRetryCount, 148 final String dataDir) { 149 if (agents == null || agents.length == 0) { 150 throw new IllegalArgumentException("At least one agent is required"); 151 } 152 153 if (batchSize <= 0) { 154 batchSize = 1; 155 } 156 final String dataDirectory = Strings.isEmpty(dataDir) ? DEFAULT_DATA_DIR : dataDir; 157 158 final StringBuilder sb = new StringBuilder("FlumePersistent["); 159 boolean first = true; 160 for (final Agent agent : agents) { 161 if (!first) { 162 sb.append(','); 163 } 164 sb.append(agent.getHost()).append(':').append(agent.getPort()); 165 first = false; 166 } 167 sb.append(']'); 168 sb.append(' ').append(dataDirectory); 169 return getManager(sb.toString(), factory, new FactoryData(name, agents, batchSize, retries, 170 connectionTimeout, requestTimeout, delayMillis, lockTimeoutRetryCount, dataDir, properties)); 171 } 172 173 @Override 174 public void send(final Event event) { 175 if (worker.isShutdown()) { 176 throw new LoggingException("Unable to record event"); 177 } 178 179 final Map<String, String> headers = event.getHeaders(); 180 final byte[] keyData = headers.get(FlumeEvent.GUID).getBytes(UTF8); 181 try { 182 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 183 final DataOutputStream daos = new DataOutputStream(baos); 184 daos.writeInt(event.getBody().length); 185 daos.write(event.getBody(), 0, event.getBody().length); 186 daos.writeInt(event.getHeaders().size()); 187 for (final Map.Entry<String, String> entry : headers.entrySet()) { 188 daos.writeUTF(entry.getKey()); 189 daos.writeUTF(entry.getValue()); 190 } 191 byte[] eventData = baos.toByteArray(); 192 if (secretKey != null) { 193 final Cipher cipher = Cipher.getInstance("AES"); 194 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 195 eventData = cipher.doFinal(eventData); 196 } 197 final Future<Integer> future = threadPool.submit(new BDBWriter(keyData, eventData, environment, database, 198 gate, dbCount, getBatchSize(), lockTimeoutRetryCount)); 199 boolean interrupted = false; 200 int ieCount = 0; 201 do { 202 try { 203 future.get(); 204 } catch (final InterruptedException ie) { 205 interrupted = true; 206 ++ieCount; 207 } 208 } while (interrupted && ieCount <= 1); 209 210 } catch (final Exception ex) { 211 throw new LoggingException("Exception occurred writing log event", ex); 212 } 213 } 214 215 @Override 216 protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 217 boolean closed = true; 218 LOGGER.debug("Shutting down FlumePersistentManager"); 219 worker.shutdown(); 220 final long requestedTimeoutMillis = timeUnit.toMillis(timeout); 221 final long shutdownWaitMillis = requestedTimeoutMillis > 0 ? requestedTimeoutMillis : SHUTDOWN_WAIT_MILLIS; 222 try { 223 worker.join(shutdownWaitMillis); 224 } catch (final InterruptedException ie) { 225 // Ignore the exception and shutdown. 226 } 227 ExecutorServices.shutdown(threadPool, shutdownWaitMillis, TimeUnit.MILLISECONDS, toString()); 228 try { 229 worker.join(); 230 } catch (final InterruptedException ex) { 231 logDebug("interrupted while waiting for worker to complete", ex); 232 } 233 try { 234 LOGGER.debug("FlumePersistenceManager dataset status: {}", database.getStats(new StatsConfig())); 235 database.close(); 236 } catch (final Exception ex) { 237 logWarn("Failed to close database", ex); 238 closed = false; 239 } 240 try { 241 environment.cleanLog(); 242 environment.close(); 243 } catch (final Exception ex) { 244 logWarn("Failed to close environment", ex); 245 closed = false; 246 } 247 return closed && super.releaseSub(timeout, timeUnit); 248 } 249 250 private void doSend(final SimpleEvent event) { 251 LOGGER.debug("Sending event to Flume"); 252 super.send(event); 253 } 254 255 /** 256 * Thread for writing to Berkeley DB to avoid having interrupts close the database. 257 */ 258 private static class BDBWriter implements Callable<Integer> { 259 private final byte[] eventData; 260 private final byte[] keyData; 261 private final Environment environment; 262 private final Database database; 263 private final Gate gate; 264 private final AtomicLong dbCount; 265 private final long batchSize; 266 private final int lockTimeoutRetryCount; 267 268 public BDBWriter(final byte[] keyData, final byte[] eventData, final Environment environment, 269 final Database database, final Gate gate, final AtomicLong dbCount, final long batchSize, 270 final int lockTimeoutRetryCount) { 271 this.keyData = keyData; 272 this.eventData = eventData; 273 this.environment = environment; 274 this.database = database; 275 this.gate = gate; 276 this.dbCount = dbCount; 277 this.batchSize = batchSize; 278 this.lockTimeoutRetryCount = lockTimeoutRetryCount; 279 } 280 281 @Override 282 public Integer call() throws Exception { 283 final DatabaseEntry key = new DatabaseEntry(keyData); 284 final DatabaseEntry data = new DatabaseEntry(eventData); 285 Exception exception = null; 286 for (int retryIndex = 0; retryIndex < lockTimeoutRetryCount; ++retryIndex) { 287 Transaction txn = null; 288 try { 289 txn = environment.beginTransaction(null, null); 290 try { 291 database.put(txn, key, data); 292 txn.commit(); 293 txn = null; 294 if (dbCount.incrementAndGet() >= batchSize) { 295 gate.open(); 296 } 297 exception = null; 298 break; 299 } catch (final LockConflictException lce) { 300 exception = lce; 301 // Fall through and retry. 302 } catch (final Exception ex) { 303 if (txn != null) { 304 txn.abort(); 305 } 306 throw ex; 307 } finally { 308 if (txn != null) { 309 txn.abort(); 310 txn = null; 311 } 312 } 313 } catch (final LockConflictException lce) { 314 exception = lce; 315 if (txn != null) { 316 try { 317 txn.abort(); 318 txn = null; 319 } catch (final Exception ex) { 320 LOGGER.trace("Ignoring exception while aborting transaction during lock conflict."); 321 } 322 } 323 324 } 325 try { 326 Thread.sleep(LOCK_TIMEOUT_SLEEP_MILLIS); 327 } catch (final InterruptedException ie) { 328 // Ignore the error 329 } 330 } 331 if (exception != null) { 332 throw exception; 333 } 334 return eventData.length; 335 } 336 } 337 338 /** 339 * Factory data. 340 */ 341 private static class FactoryData { 342 private final String name; 343 private final Agent[] agents; 344 private final int batchSize; 345 private final String dataDir; 346 private final int retries; 347 private final int connectionTimeout; 348 private final int requestTimeout; 349 private final int delayMillis; 350 private final int lockTimeoutRetryCount; 351 private final Property[] properties; 352 353 /** 354 * Constructor. 355 * @param name The name of the Appender. 356 * @param agents The agents. 357 * @param batchSize The number of events to include in a batch. 358 * @param dataDir The directory for data. 359 */ 360 public FactoryData(final String name, final Agent[] agents, final int batchSize, final int retries, 361 final int connectionTimeout, final int requestTimeout, final int delayMillis, 362 final int lockTimeoutRetryCount, final String dataDir, final Property[] properties) { 363 this.name = name; 364 this.agents = agents; 365 this.batchSize = batchSize; 366 this.dataDir = dataDir; 367 this.retries = retries; 368 this.connectionTimeout = connectionTimeout; 369 this.requestTimeout = requestTimeout; 370 this.delayMillis = delayMillis; 371 this.lockTimeoutRetryCount = lockTimeoutRetryCount; 372 this.properties = properties; 373 } 374 } 375 376 /** 377 * Avro Manager Factory. 378 */ 379 private static class BDBManagerFactory implements ManagerFactory<FlumePersistentManager, FactoryData> { 380 381 /** 382 * Create the FlumeKratiManager. 383 * @param name The name of the entity to manage. 384 * @param data The data required to create the entity. 385 * @return The FlumeKratiManager. 386 */ 387 @Override 388 public FlumePersistentManager createManager(final String name, final FactoryData data) { 389 SecretKey secretKey = null; 390 Database database = null; 391 Environment environment = null; 392 393 final Map<String, String> properties = new HashMap<>(); 394 if (data.properties != null) { 395 for (final Property property : data.properties) { 396 properties.put(property.getName(), property.getValue()); 397 } 398 } 399 400 try { 401 final File dir = new File(data.dataDir); 402 FileUtils.mkdir(dir, true); 403 final EnvironmentConfig dbEnvConfig = new EnvironmentConfig(); 404 dbEnvConfig.setTransactional(true); 405 dbEnvConfig.setAllowCreate(true); 406 dbEnvConfig.setLockTimeout(5, TimeUnit.SECONDS); 407 environment = new Environment(dir, dbEnvConfig); 408 final DatabaseConfig dbConfig = new DatabaseConfig(); 409 dbConfig.setTransactional(true); 410 dbConfig.setAllowCreate(true); 411 database = environment.openDatabase(null, name, dbConfig); 412 } catch (final Exception ex) { 413 LOGGER.error("Could not create FlumePersistentManager", ex); 414 // For consistency, close database as well as environment even though it should never happen since the 415 // database is that last thing in the block above, but this does guard against a future line being 416 // inserted at the end that would bomb (like some debug logging). 417 if (database != null) { 418 database.close(); 419 database = null; 420 } 421 if (environment != null) { 422 environment.close(); 423 environment = null; 424 } 425 return null; 426 } 427 428 try { 429 String key = null; 430 for (final Map.Entry<String, String> entry : properties.entrySet()) { 431 if (entry.getKey().equalsIgnoreCase(KEY_PROVIDER)) { 432 key = entry.getValue(); 433 break; 434 } 435 } 436 if (key != null) { 437 final PluginManager manager = new PluginManager("KeyProvider"); 438 manager.collectPlugins(); 439 final Map<String, PluginType<?>> plugins = manager.getPlugins(); 440 if (plugins != null) { 441 boolean found = false; 442 for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) { 443 if (entry.getKey().equalsIgnoreCase(key)) { 444 found = true; 445 final Class<?> cl = entry.getValue().getPluginClass(); 446 try { 447 final SecretKeyProvider provider = (SecretKeyProvider) cl.newInstance(); 448 secretKey = provider.getSecretKey(); 449 LOGGER.debug("Persisting events using SecretKeyProvider {}", cl.getName()); 450 } catch (final Exception ex) { 451 LOGGER.error("Unable to create SecretKeyProvider {}, encryption will be disabled", 452 cl.getName()); 453 } 454 break; 455 } 456 } 457 if (!found) { 458 LOGGER.error("Unable to locate SecretKey provider {}, encryption will be disabled", key); 459 } 460 } else { 461 LOGGER.error("Unable to locate SecretKey provider {}, encryption will be disabled", key); 462 } 463 } 464 } catch (final Exception ex) { 465 LOGGER.warn("Error setting up encryption - encryption will be disabled", ex); 466 } 467 return new FlumePersistentManager(name, data.name, data.agents, data.batchSize, data.retries, 468 data.connectionTimeout, data.requestTimeout, data.delayMillis, database, environment, secretKey, 469 data.lockTimeoutRetryCount); 470 } 471 } 472 473 /** 474 * Thread that sends data to Flume and pulls it from Berkeley DB. 475 */ 476 private static class WriterThread extends Log4jThread { 477 private volatile boolean shutdown = false; 478 private final Database database; 479 private final Environment environment; 480 private final FlumePersistentManager manager; 481 private final Gate gate; 482 private final SecretKey secretKey; 483 private final int batchSize; 484 private final AtomicLong dbCounter; 485 private final int lockTimeoutRetryCount; 486 487 public WriterThread(final Database database, final Environment environment, 488 final FlumePersistentManager manager, final Gate gate, final int batchsize, 489 final SecretKey secretKey, final AtomicLong dbCount, final int lockTimeoutRetryCount) { 490 super("FlumePersistentManager-Writer"); 491 this.database = database; 492 this.environment = environment; 493 this.manager = manager; 494 this.gate = gate; 495 this.batchSize = batchsize; 496 this.secretKey = secretKey; 497 this.setDaemon(true); 498 this.dbCounter = dbCount; 499 this.lockTimeoutRetryCount = lockTimeoutRetryCount; 500 } 501 502 public void shutdown() { 503 LOGGER.debug("Writer thread shutting down"); 504 this.shutdown = true; 505 gate.open(); 506 } 507 508 public boolean isShutdown() { 509 return shutdown; 510 } 511 512 @Override 513 public void run() { 514 LOGGER.trace("WriterThread started - batch size = " + batchSize + ", delayMillis = " + manager.getDelayMillis()); 515 long nextBatchMillis = System.currentTimeMillis() + manager.getDelayMillis(); 516 while (!shutdown) { 517 final long nowMillis = System.currentTimeMillis(); 518 final long dbCount = database.count(); 519 dbCounter.set(dbCount); 520 if (dbCount >= batchSize || dbCount > 0 && nextBatchMillis <= nowMillis) { 521 nextBatchMillis = nowMillis + manager.getDelayMillis(); 522 try { 523 boolean errors = false; 524 final DatabaseEntry key = new DatabaseEntry(); 525 final DatabaseEntry data = new DatabaseEntry(); 526 527 gate.close(); 528 OperationStatus status; 529 if (batchSize > 1) { 530 try { 531 errors = sendBatch(key, data); 532 } catch (final Exception ex) { 533 break; 534 } 535 } else { 536 Exception exception = null; 537 for (int retryIndex = 0; retryIndex < lockTimeoutRetryCount; ++retryIndex) { 538 exception = null; 539 Transaction txn = null; 540 Cursor cursor = null; 541 try { 542 txn = environment.beginTransaction(null, null); 543 cursor = database.openCursor(txn, null); 544 try { 545 status = cursor.getFirst(key, data, LockMode.RMW); 546 while (status == OperationStatus.SUCCESS) { 547 final SimpleEvent event = createEvent(data); 548 if (event != null) { 549 try { 550 manager.doSend(event); 551 } catch (final Exception ioe) { 552 errors = true; 553 LOGGER.error("Error sending event", ioe); 554 break; 555 } 556 try { 557 cursor.delete(); 558 } catch (final Exception ex) { 559 LOGGER.error("Unable to delete event", ex); 560 } 561 } 562 status = cursor.getNext(key, data, LockMode.RMW); 563 } 564 if (cursor != null) { 565 cursor.close(); 566 cursor = null; 567 } 568 txn.commit(); 569 txn = null; 570 dbCounter.decrementAndGet(); 571 exception = null; 572 break; 573 } catch (final LockConflictException lce) { 574 exception = lce; 575 // Fall through and retry. 576 } catch (final Exception ex) { 577 LOGGER.error("Error reading or writing to database", ex); 578 shutdown = true; 579 break; 580 } finally { 581 if (cursor != null) { 582 cursor.close(); 583 cursor = null; 584 } 585 if (txn != null) { 586 txn.abort(); 587 txn = null; 588 } 589 } 590 } catch (final LockConflictException lce) { 591 exception = lce; 592 if (cursor != null) { 593 try { 594 cursor.close(); 595 cursor = null; 596 } catch (final Exception ex) { 597 LOGGER.trace("Ignored exception closing cursor during lock conflict."); 598 } 599 } 600 if (txn != null) { 601 try { 602 txn.abort(); 603 txn = null; 604 } catch (final Exception ex) { 605 LOGGER.trace("Ignored exception aborting tx during lock conflict."); 606 } 607 } 608 } 609 try { 610 Thread.sleep(LOCK_TIMEOUT_SLEEP_MILLIS); 611 } catch (final InterruptedException ie) { 612 // Ignore the error 613 } 614 } 615 if (exception != null) { 616 LOGGER.error("Unable to read or update data base", exception); 617 } 618 } 619 if (errors) { 620 Thread.sleep(manager.getDelayMillis()); 621 continue; 622 } 623 } catch (final Exception ex) { 624 LOGGER.warn("WriterThread encountered an exception. Continuing.", ex); 625 } 626 } else { 627 if (nextBatchMillis <= nowMillis) { 628 nextBatchMillis = nowMillis + manager.getDelayMillis(); 629 } 630 try { 631 final long interval = nextBatchMillis - nowMillis; 632 gate.waitForOpen(interval); 633 } catch (final InterruptedException ie) { 634 LOGGER.warn("WriterThread interrupted, continuing"); 635 } catch (final Exception ex) { 636 LOGGER.error("WriterThread encountered an exception waiting for work", ex); 637 break; 638 } 639 } 640 } 641 642 if (batchSize > 1 && database.count() > 0) { 643 final DatabaseEntry key = new DatabaseEntry(); 644 final DatabaseEntry data = new DatabaseEntry(); 645 try { 646 sendBatch(key, data); 647 } catch (final Exception ex) { 648 LOGGER.warn("Unable to write final batch"); 649 } 650 } 651 LOGGER.trace("WriterThread exiting"); 652 } 653 654 private boolean sendBatch(DatabaseEntry key, final DatabaseEntry data) throws Exception { 655 boolean errors = false; 656 OperationStatus status; 657 Cursor cursor = null; 658 try { 659 final BatchEvent batch = new BatchEvent(); 660 for (int retryIndex = 0; retryIndex < lockTimeoutRetryCount; ++retryIndex) { 661 try { 662 cursor = database.openCursor(null, CursorConfig.DEFAULT); 663 status = cursor.getFirst(key, data, null); 664 665 for (int i = 0; status == OperationStatus.SUCCESS && i < batchSize; ++i) { 666 final SimpleEvent event = createEvent(data); 667 if (event != null) { 668 batch.addEvent(event); 669 } 670 status = cursor.getNext(key, data, null); 671 } 672 break; 673 } catch (final LockConflictException lce) { 674 if (cursor != null) { 675 try { 676 cursor.close(); 677 cursor = null; 678 } catch (final Exception ex) { 679 LOGGER.trace("Ignored exception closing cursor during lock conflict."); 680 } 681 } 682 } 683 } 684 685 try { 686 manager.send(batch); 687 } catch (final Exception ioe) { 688 LOGGER.error("Error sending events", ioe); 689 errors = true; 690 } 691 if (!errors) { 692 if (cursor != null) { 693 cursor.close(); 694 cursor = null; 695 } 696 Transaction txn = null; 697 Exception exception = null; 698 for (int retryIndex = 0; retryIndex < lockTimeoutRetryCount; ++retryIndex) { 699 try { 700 txn = environment.beginTransaction(null, null); 701 try { 702 for (final Event event : batch.getEvents()) { 703 try { 704 final Map<String, String> headers = event.getHeaders(); 705 key = new DatabaseEntry(headers.get(FlumeEvent.GUID).getBytes(UTF8)); 706 database.delete(txn, key); 707 } catch (final Exception ex) { 708 LOGGER.error("Error deleting key from database", ex); 709 } 710 } 711 txn.commit(); 712 long count = dbCounter.get(); 713 while (!dbCounter.compareAndSet(count, count - batch.getEvents().size())) { 714 count = dbCounter.get(); 715 } 716 exception = null; 717 break; 718 } catch (final LockConflictException lce) { 719 exception = lce; 720 if (cursor != null) { 721 try { 722 cursor.close(); 723 cursor = null; 724 } catch (final Exception ex) { 725 LOGGER.trace("Ignored exception closing cursor during lock conflict."); 726 } 727 } 728 if (txn != null) { 729 try { 730 txn.abort(); 731 txn = null; 732 } catch (final Exception ex) { 733 LOGGER.trace("Ignored exception aborting transaction during lock conflict."); 734 } 735 } 736 } catch (final Exception ex) { 737 LOGGER.error("Unable to commit transaction", ex); 738 if (txn != null) { 739 txn.abort(); 740 } 741 } 742 } catch (final LockConflictException lce) { 743 exception = lce; 744 if (cursor != null) { 745 try { 746 cursor.close(); 747 cursor = null; 748 } catch (final Exception ex) { 749 LOGGER.trace("Ignored exception closing cursor during lock conflict."); 750 } 751 } 752 if (txn != null) { 753 try { 754 txn.abort(); 755 txn = null; 756 } catch (final Exception ex) { 757 LOGGER.trace("Ignored exception aborting transaction during lock conflict."); 758 } 759 } 760 } finally { 761 if (cursor != null) { 762 cursor.close(); 763 cursor = null; 764 } 765 if (txn != null) { 766 txn.abort(); 767 txn = null; 768 } 769 } 770 try { 771 Thread.sleep(LOCK_TIMEOUT_SLEEP_MILLIS); 772 } catch (final InterruptedException ie) { 773 // Ignore the error 774 } 775 } 776 if (exception != null) { 777 LOGGER.error("Unable to delete events from data base", exception); 778 } 779 } 780 } catch (final Exception ex) { 781 LOGGER.error("Error reading database", ex); 782 shutdown = true; 783 throw ex; 784 } finally { 785 if (cursor != null) { 786 cursor.close(); 787 } 788 } 789 790 return errors; 791 } 792 793 private SimpleEvent createEvent(final DatabaseEntry data) { 794 final SimpleEvent event = new SimpleEvent(); 795 try { 796 byte[] eventData = data.getData(); 797 if (secretKey != null) { 798 final Cipher cipher = Cipher.getInstance("AES"); 799 cipher.init(Cipher.DECRYPT_MODE, secretKey); 800 eventData = cipher.doFinal(eventData); 801 } 802 final ByteArrayInputStream bais = new ByteArrayInputStream(eventData); 803 final DataInputStream dais = new DataInputStream(bais); 804 int length = dais.readInt(); 805 final byte[] bytes = new byte[length]; 806 dais.read(bytes, 0, length); 807 event.setBody(bytes); 808 length = dais.readInt(); 809 final Map<String, String> map = new HashMap<>(length); 810 for (int i = 0; i < length; ++i) { 811 final String headerKey = dais.readUTF(); 812 final String value = dais.readUTF(); 813 map.put(headerKey, value); 814 } 815 event.setHeaders(map); 816 return event; 817 } catch (final Exception ex) { 818 LOGGER.error("Error retrieving event", ex); 819 return null; 820 } 821 } 822 823 } 824 825 /** 826 * An internal class. 827 */ 828 private static class Gate { 829 830 private boolean isOpen = false; 831 832 public boolean isOpen() { 833 return isOpen; 834 } 835 836 public synchronized void open() { 837 isOpen = true; 838 notifyAll(); 839 } 840 841 public synchronized void close() { 842 isOpen = false; 843 } 844 845 public synchronized void waitForOpen(final long timeout) throws InterruptedException { 846 wait(timeout); 847 } 848 } 849}