/*
* (C) Copyright 2010, 2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*/
package org.nuxeo.ecm.platform.audit.service;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.platform.audit.api.LogEntry;
import org.nuxeo.ecm.platform.audit.service.extension.AuditBulkerDescriptor;
import org.nuxeo.ecm.platform.audit.service.management.AuditBulkerMBean;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.management.ResourcePublisher;
import org.nuxeo.runtime.metrics.MetricsService;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
public class DefaultAuditBulker implements AuditBulkerMBean, AuditBulker {
final Log log = LogFactory.getLog(DefaultAuditBulker.class);
final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
final Gauge<Integer> sizeGauge = new Gauge<Integer>() {
@Override
public Integer getValue() {
return queue.size();
}
};
final AuditBackend backend;
final Counter queuedCount = registry.counter(MetricRegistry.name("nuxeo", "audit", "queued"));
final Counter drainedCount = registry.counter(MetricRegistry.name("nuxeo", "audit", "drained"));
int timeout;
int bulksize;
Thread thread;
DefaultAuditBulker(AuditBackend backend, AuditBulkerDescriptor config) {
this.backend = backend;
timeout = config.timeout;
bulksize = config.size;
}
@Override
public void onApplicationStarted() {
thread = new Thread(new Consumer(), "Nuxeo-Audit-Bulker");
thread.start();
ResourcePublisher publisher = Framework.getService(ResourcePublisher.class);
if (publisher != null) {
publisher.registerResource("audit-bulker", "audit-bulker", AuditBulkerMBean.class, this);
}
registry.register(MetricRegistry.name("nuxeo", "audit", "size"), sizeGauge);
}
@Override
public void onApplicationStopped() {
registry.remove(MetricRegistry.name("nuxeo", "audit", "size"));
ResourcePublisher publisher = Framework.getService(ResourcePublisher.class);
if (publisher != null) {
publisher.unregisterResource("audit-bulker", "audit-bulker");
}
stopped = true;
try {
thread.interrupt();
} finally {
thread = null;
}
}
final AtomicInteger size = new AtomicInteger(0);
final ReentrantLock lock = new ReentrantLock();
final Condition isEmpty = lock.newCondition();
final Condition isFilled = lock.newCondition();
final Queue<LogEntry> queue = new ConcurrentLinkedQueue<>();
volatile boolean stopped;
@Override
public void offer(LogEntry entry) {
if (log.isDebugEnabled()) {
log.debug("offered " + entry);
}
queue.add(entry);
queuedCount.inc();
if (size.incrementAndGet() >= bulksize) {
lock.lock();
try {
isFilled.signalAll();
} finally {
lock.unlock();
}
}
}
@Override
public boolean await(long time, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
isFilled.signalAll();
return isEmpty.await(time, unit);
} finally {
lock.unlock();
}
}
int drain() {
List<LogEntry> entries = new LinkedList<>();
while (!queue.isEmpty()) {
entries.add(queue.remove());
}
backend.addLogEntries(entries);
int delta = entries.size();
size.addAndGet(-delta);
drainedCount.inc(delta);
return delta;
}
class Consumer implements Runnable {
@Override
public void run() {
log.info("bulk audit logger started");
while (!stopped) {
lock.lock();
try {
isFilled.await(timeout, TimeUnit.MILLISECONDS);
if (queue.isEmpty()) {
continue;
}
int count = drain();
if (log.isDebugEnabled()) {
log.debug("flushed " + count + " events");
}
} catch (InterruptedException cause) {
Thread.currentThread().interrupt();
return;
} finally {
isEmpty.signalAll();
lock.unlock();
}
}
log.info("bulk audit logger stopped");
}
}
@Override
public int getBulkTimeout() {
return timeout;
}
@Override
public void setBulkTimeout(int value) {
timeout = value;
}
@Override
public int getBulkSize() {
return bulksize;
}
@Override
public void setBulkSize(int value) {
bulksize = value;
}
@Override
public void resetMetrics() {
queuedCount.dec(queuedCount.getCount());
drainedCount.dec(drainedCount.getCount());
}
}