/* * 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-2010 Sun Microsystems, Inc. */ package org.opends.server.protocols.asn1; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.concurrent.locks.ReentrantLock; import org.opends.server.types.ByteSequence; /** * This class is for writing ASN.1 elements directly to an * NIO WritableByteChannel with an embedded ByteBuffer. * The NIO ByteBuffer will be flushed to the channel automatically * when full or when the flush() method is called. */ final class ASN1ByteChannelWriter implements ASN1Writer { // The byte channel to write to. private final WritableByteChannel byteChannel; // The wrapped ASN.1 OutputStream writer. private final ASN1OutputStreamWriter writer; // The NIO ByteStringBuilder to write to. private final ByteBuffer byteBuffer; // The lock to ensure atomic message flush. private final ReentrantLock flushLock; /** * An adaptor class provides a streaming interface to write to a * NIO ByteBuffer. This class is also responsible for writing * the ByteBuffer out to the channel when full and clearing it. */ private class ByteBufferOutputStream extends OutputStream { /** * {@inheritDoc} */ @Override public void write(int i) throws IOException { if(!byteBuffer.hasRemaining()) { // No more space left in the buffer, send out to the channel. ASN1ByteChannelWriter.this.lockAndFlush(); } byteBuffer.put((byte)i); } /** * {@inheritDoc} */ @Override public void write(byte[] bytes) throws IOException { write(bytes, 0, bytes.length); } /** * {@inheritDoc} */ @Override public void write(byte[] bytes, int i, int i1) throws IOException { if(i < 0 || i1 < 0 || i + i1 > bytes.length) throw new IndexOutOfBoundsException(); int bytesToWrite = i1; int len; while(bytesToWrite > 0) { len = byteBuffer.remaining(); if(len < bytesToWrite) { byteBuffer.put(bytes, i + i1 - bytesToWrite, len); bytesToWrite -= len; // No more space left in the buffer, send out to the channel. ASN1ByteChannelWriter.this.lockAndFlush(); } else { byteBuffer.put(bytes, i + i1 - bytesToWrite, bytesToWrite); bytesToWrite = 0; } } } } /** * Constructs a new ASN1ByteChannelWriter. * * @param byteChannel The WritableByteChannel to write to. * @param writeLock The write lock to use when flushing to the destination. * @param writeBufferSize The NIO ByteBuffer size. */ ASN1ByteChannelWriter(WritableByteChannel byteChannel, ReentrantLock writeLock, int writeBufferSize) { this.byteChannel = byteChannel; this.byteBuffer = ByteBuffer.allocate(writeBufferSize); this.flushLock = writeLock; ByteBufferOutputStream bufferStream = new ByteBufferOutputStream(); this.writer = new ASN1OutputStreamWriter(bufferStream); } /** * {@inheritDoc} */ public ASN1Writer writeBoolean(boolean booleanValue) throws IOException { writer.writeBoolean(booleanValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeBoolean(byte type, boolean booleanValue) throws IOException { writer.writeBoolean(type, booleanValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeEndSet() throws IOException { writer.writeEndSet(); return this; } /** * {@inheritDoc} */ public ASN1Writer writeEndSequence() throws IOException { writer.writeEndSequence(); return this; } /** * {@inheritDoc} */ public ASN1Writer writeInteger(int intValue) throws IOException { writer.writeInteger(intValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeInteger(long longValue) throws IOException { writer.writeInteger(longValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeInteger(byte type, int intValue) throws IOException { writer.writeInteger(type, intValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeInteger(byte type, long longValue) throws IOException { writer.writeInteger(type, longValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeEnumerated(int intValue) throws IOException { writer.writeEnumerated(intValue); return this; } /** * {@inheritDoc} */ public ASN1Writer writeNull() throws IOException { writer.writeNull(); return this; } /** * {@inheritDoc} */ public ASN1Writer writeNull(byte type) throws IOException { writer.writeNull(type); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(byte type, byte[] value, int offset, int length) throws IOException { writer.writeOctetString(type, value, offset, length); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(byte type, ByteSequence value) throws IOException { writer.writeOctetString(type, value); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(byte type, String value) throws IOException { writer.writeOctetString(type, value); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(byte[] value, int offset, int length) throws IOException { writer.writeOctetString(value, offset, length); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(ByteSequence value) throws IOException { writer.writeOctetString(value); return this; } /** * {@inheritDoc} */ public ASN1Writer writeOctetString(String value) throws IOException { writer.writeOctetString(value); return this; } /** * {@inheritDoc} */ public ASN1Writer writeStartSequence() throws IOException { writer.writeStartSequence(); return this; } /** * {@inheritDoc} */ public ASN1Writer writeStartSequence(byte type) throws IOException { writer.writeStartSequence(type); return this; } /** * {@inheritDoc} */ public ASN1Writer writeStartSet() throws IOException { writer.writeStartSet(); return this; } /** * {@inheritDoc} */ public ASN1Writer writeStartSet(byte type) throws IOException { writer.writeStartSet(type); return this; } /** * Flush the entire contents of the NIO ByteBuffer out to the * channel. Note that the caller should only invoke this * method when it has finished writing the entire message and * not earlier. Otherwise, calling this method prematurely * while sharing this writer among several threads can cause * messages to interleave. While it is possible to ensure the * safety by either single threaded usage or using external * locking its generally not advised to do so and the caller * should instead obey no flush until the entire message is * written rule when using this writer. * * @throws IOException If an error occurs while flushing. */ public void flush() throws IOException { byteBuffer.flip(); if (!flushLock.isHeldByCurrentThread()) { flushLock.lock(); } try { byteChannel.write(byteBuffer); } finally { flushLock.unlock(); } byteBuffer.clear(); } /** * Take the flush lock and flush the entire contents of * the NIO ByteBuffer out to the channel. * * @throws IOException If an error occurs while flushing. */ private void lockAndFlush() throws IOException { byteBuffer.flip(); if (!flushLock.isHeldByCurrentThread()) { flushLock.lock(); } try { while (byteBuffer.hasRemaining()) { byteChannel.write(byteBuffer); } } catch (IOException e) { flushLock.unlock(); throw e; } byteBuffer.clear(); } /** * Closes this ASN.1 writer and the underlying channel. * * @throws IOException if an error occurs while closing the writer. */ public void close() throws IOException { // Close the writer first to flush the writer to the NIO byte buffer. writer.close(); flush(); byteChannel.close(); } }