net.sf.jacclog.service.importer.internal.queue.LogEntryQueuePersisterObserver.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jacclog.service.importer.internal.queue.LogEntryQueuePersisterObserver.java

Source

/*******************************************************************************
 * Copyright 2011 Andr Roul
 * 
 * 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.");
    }

}