/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions Copyright 2013 ForgeRock AS.
*/
package org.opends.server.loggers;
import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opends.messages.Message;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.core.DirectoryServer;
/**
* A Text Writer which writes log records asynchronously to
* character-based stream.
*/
public class AsynchronousTextWriter
implements ServerShutdownListener, TextWriter
{
/**
* The wrapped Text Writer.
*/
private final TextWriter writer;
/** Queue to store unpublished records. */
private final LinkedBlockingQueue<String> queue;
/** The capacity for the queue. */
private final int capacity;
private String name;
private AtomicBoolean stopRequested;
private WriterThread writerThread;
private boolean autoFlush;
/**
* Construct a new AsynchronousTextWriter wrapper.
*
* @param name the name of the thread.
* @param capacity the size of the queue before it gets flushed.
* @param autoFlush indicates if the underlying writer should be flushed
* after the queue is flushed.
* @param writer a character stream used for output.
*/
public AsynchronousTextWriter(String name, int capacity, boolean autoFlush,
TextWriter writer)
{
this.name = name;
this.autoFlush = autoFlush;
this.writer = writer;
this.queue = new LinkedBlockingQueue<String>(capacity);
this.capacity = capacity;
this.writerThread = null;
this.stopRequested = new AtomicBoolean(false);
writerThread = new WriterThread();
writerThread.start();
DirectoryServer.registerShutdownListener(this);
}
/**
* The publisher thread is responsible for emptying the queue of log records
* waiting to published.
*/
private class WriterThread extends DirectoryThread
{
public WriterThread()
{
super(name);
}
/**
* the run method of the writerThread. Run until queue is empty
* AND we've been asked to terminate
*/
public void run()
{
ArrayList<String> drainList = new ArrayList<String>(capacity);
String message = null;
while (!stopRequested.get() || !queue.isEmpty()) {
try
{
queue.drainTo(drainList, capacity);
if (drainList.isEmpty())
{
message = queue.poll(10, TimeUnit.SECONDS);
if(message != null)
{
do
{
writer.writeRecord(message);
message = queue.poll();
}
while(message != null);
if(autoFlush)
{
flush();
}
}
}
else
{
for (String record : drainList)
{
writer.writeRecord(record);
}
drainList.clear();
if (autoFlush)
{
flush();
}
}
}
catch (InterruptedException ex) {
// Ignore. We'll rerun the loop
// and presumably fall out.
}
}
}
}
/**
* Write the log record asyncronously.
*
* @param record the log record to write.
*/
public void writeRecord(String record)
{
// No writer? Off to the bit bucket.
if (writer != null) {
while (!stopRequested.get())
{
// Put request on queue for writer
try
{
queue.put(record);
break;
}
catch(InterruptedException e)
{
// We expect this to happen. Just ignore it and hopefully
// drop out in the next try.
}
}
}
}
/**
* {@inheritDoc}
*/
public void flush()
{
writer.flush();
}
/**
* {@inheritDoc}
*/
public long getBytesWritten()
{
return writer.getBytesWritten();
}
/**
* Retrieves the wrapped writer.
*
* @return The wrapped writer used by this asynchronous writer.
*/
public TextWriter getWrappedWriter()
{
return writer;
}
/**
* {@inheritDoc}
*/
public String getShutdownListenerName()
{
return "AsynchronousTextWriter Thread " + name;
}
/**
* {@inheritDoc}
*/
public void processServerShutdown(Message reason)
{
// Don't shutdown the wrapped writer on server shutdown as it
// might get more write requests before the log publishers are
// manually shutdown just before the server process exists.
shutdown(false);
}
/**
* {@inheritDoc}
*/
public void shutdown()
{
shutdown(true);
}
/**
* Releases any resources held by the writer.
*
* @param shutdownWrapped If the wrapped writer should be closed as well.
*/
public void shutdown(boolean shutdownWrapped)
{
stopRequested.set(true);
// Wait for publisher thread to terminate
while (writerThread != null && writerThread.isAlive()) {
try {
// Interrupt the thread if its blocking
writerThread.interrupt();
writerThread.join();
}
catch (InterruptedException ex) {
// Ignore; we gotta wait..
}
}
// The writer writerThread SHOULD have drained the queue.
// If not, handle outstanding requests ourselves,
// and push them to the writer.
while (!queue.isEmpty()) {
String message = queue.poll();
writer.writeRecord(message);
}
// Shutdown the wrapped writer.
if (shutdownWrapped && writer != null) writer.shutdown();
DirectoryServer.deregisterShutdownListener(this);
}
/**
* Set the auto flush setting for this writer.
*
* @param autoFlush If the writer should flush the buffer after every line.
*/
public void setAutoFlush(boolean autoFlush)
{
this.autoFlush = autoFlush;
}
}