/*******************************************************************************
* Copyright 2011 André Rouél
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package net.sf.jacclog.service.importer.internal.queue;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import net.sf.jacclog.api.domain.ReadonlyLogEntry;
import net.sf.jacclog.service.importer.api.service.LogEntryImportService;
import net.sf.jacclog.util.observer.BlockingQueueObserver;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogEntryQueuePersisterObserver implements BlockingQueueObserver<ReadonlyLogEntry> {
public static class LogEntryPersisterTask implements Runnable {
private static final int MAX_ATTEMPTS = 3;
private final BlockingQueue<ReadonlyLogEntry> queue;
private final LogEntryImportService<ReadonlyLogEntry> service;
public LogEntryPersisterTask(final LogEntryImportService<ReadonlyLogEntry> service,
final BlockingQueue<ReadonlyLogEntry> queue) {
if (queue == null) {
throw new IllegalArgumentException("Argument 'queue' can not be null.");
}
if (service == null) {
throw new IllegalArgumentException("Argument 'service' can not be null.");
}
this.queue = queue;
this.service = service;
}
private boolean persist(final Collection<ReadonlyLogEntry> entries) {
boolean result = false;
try {
service.create(entries);
result = true;
} catch (final Exception e) {
// no problem with an exception here, we try it multiple times
LOG.info("Persisting failed: " + e.getLocalizedMessage());
}
return result;
}
private boolean persistWithMultipleAttempts(final Collection<ReadonlyLogEntry> entries) {
boolean isPersisted = false;
if (!entries.isEmpty()) {
for (int i = 0; i < MAX_ATTEMPTS; i++) {
isPersisted = persist(entries);
if (isPersisted) {
break;
}
}
}
return isPersisted;
}
@Override
public void run() {
if (!queue.isEmpty()) {
// pause for a while so that more entries can be persist
try {
Thread.sleep(100l);
} catch (final InterruptedException e1) {
LOG.warn(e1.getLocalizedMessage(), e1);
}
// take all available entries from the queue
final Collection<ReadonlyLogEntry> entries = new ArrayDeque<ReadonlyLogEntry>();
ReadonlyLogEntry entry = null;
int count = 0;
do {
entry = queue.poll();
if (entry != null) {
entries.add(entry);
count++;
}
} while (entry != null && count < BATCH_SIZE);
if (!entries.isEmpty()) {
if (!persistWithMultipleAttempts(entries)) {
LOG.warn(entries.size() + " entries were not stored in the repository.");
}
}
}
}
}
public static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(UncaughtExceptionHandler.class);
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
LOG.warn("An unexpected error has occurred in Thread '" + thread.getName() + "'.", throwable);
}
}
private static final Logger LOG = LoggerFactory.getLogger(LogEntryQueuePersisterObserver.class);
private final ExecutorService executor;
private final LogEntryImportService<ReadonlyLogEntry> service;
private final AtomicInteger counter = new AtomicInteger();
private static final int BATCH_SIZE = 1000;
public LogEntryQueuePersisterObserver(final LogEntryImportService<ReadonlyLogEntry> service) {
if (service == null) {
throw new IllegalArgumentException("Argument 'service' can not be null.");
}
this.service = service;
final BasicThreadFactory factory = new BasicThreadFactory.Builder()
// attributes
.namingPattern("persister-%d").daemon(true).priority(Thread.MAX_PRIORITY)
.uncaughtExceptionHandler(new UncaughtExceptionHandler()).build();
executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), factory);
}
@Override
public void added(final BlockingQueue<ReadonlyLogEntry> queue, final ReadonlyLogEntry element) {
LOG.debug("Added entry '" + element.hashCode() + "' to queue.");
if (counter.decrementAndGet() <= 0) {
counter.set(BATCH_SIZE);
executor.execute(new LogEntryPersisterTask(service, queue));
}
}
@Override
public void empty(final BlockingQueue<ReadonlyLogEntry> queue) {
LOG.debug("Log entry queue is empty. (size: " + queue.size() + ")");
// cleaning task
executor.execute(new LogEntryPersisterTask(service, queue));
}
@Override
public void removed(final BlockingQueue<ReadonlyLogEntry> queue, final ReadonlyLogEntry entry) {
LOG.debug("Removed entry '" + entry.hashCode() + "' from queue.");
}
}