001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS
026 */
027package org.opends.server.tools.makeldif;
028
029
030
031import java.io.ByteArrayOutputStream;
032import java.io.InputStream;
033import java.io.IOException;
034import java.nio.ByteBuffer;
035import java.util.concurrent.LinkedBlockingQueue;
036import java.util.concurrent.TimeUnit;
037
038import org.opends.server.types.LDIFExportConfig;
039import org.opends.server.util.LDIFException;
040import org.opends.server.util.LDIFWriter;
041
042
043
044/**
045 * This class creates an input stream that can be used to read entries generated
046 * by MakeLDIF as if they were being read from another source like a file.  It
047 * has a fixed-size queue that dictates how many entries may be held in memory
048 * at any given time.
049 */
050public class MakeLDIFInputStream
051       extends InputStream
052       implements EntryWriter
053{
054  /** Indicates whether all of the entries have been generated. */
055  private boolean allGenerated;
056
057  /** Indicates whether this input stream has been closed. */
058  private boolean closed;
059
060  /**
061   * The byte array output stream that will be used to convert entries to byte
062   * arrays with their LDIF representations.
063   */
064  private ByteArrayOutputStream entryOutputStream;
065
066  /**
067   * The byte array that will hold the LDIF representation of the next entry to
068   * be read.
069   */
070  private ByteBuffer entryBytes;
071
072  /** The IOException that should be thrown the next time a read is requested. */
073  private IOException ioException;
074
075  /** The LDIF writer that will be used to write the entries to LDIF. */
076  private LDIFWriter ldifWriter;
077
078  /** The queue used to hold generated entries until they can be read. */
079  private LinkedBlockingQueue<TemplateEntry> entryQueue;
080
081  /** The background thread being used to actually generate the entries. */
082  private MakeLDIFInputStreamThread generatorThread;
083
084  /** The template file to use to generate the entries. */
085  private TemplateFile templateFile;
086
087
088
089  /**
090   * Creates a new MakeLDIF input stream that will generate entries based on the
091   * provided template file.
092   *
093   * @param  templateFile  The template file to use to generate the entries.
094   */
095  public MakeLDIFInputStream(TemplateFile templateFile)
096  {
097    this.templateFile = templateFile;
098
099    allGenerated = false;
100    closed       = false;
101    entryQueue   = new LinkedBlockingQueue<>(10);
102    ioException  = null;
103    entryBytes   = null;
104
105    entryOutputStream = new ByteArrayOutputStream(8192);
106    LDIFExportConfig exportConfig = new LDIFExportConfig(entryOutputStream);
107
108    try
109    {
110      ldifWriter = new LDIFWriter(exportConfig);
111    }
112    catch (IOException ioe)
113    {
114      // This should never happen.
115      ioException = ioe;
116    }
117
118    generatorThread = new MakeLDIFInputStreamThread(this, templateFile);
119    generatorThread.start();
120  }
121
122
123
124  /**
125   * Closes this input stream so that no more data may be read from it.
126   */
127  public void close()
128  {
129    closed      = true;
130    ioException = null;
131  }
132
133
134
135  /**
136   * Reads a single byte of data from this input stream.
137   *
138   * @return  The byte read from the input stream, or -1 if the end of the
139   *          stream has been reached.
140   *
141   * @throws  IOException  If a problem has occurred while generating data for
142   *                       use by this input stream.
143   */
144  public int read()
145         throws IOException
146  {
147    if (closed)
148    {
149      return -1;
150    }
151    else if (ioException != null)
152    {
153      throw ioException;
154    }
155
156    if ((entryBytes == null || !entryBytes.hasRemaining())
157        && !getNextEntry())
158    {
159      closed = true;
160      return -1;
161    }
162
163    return 0xFF & entryBytes.get();
164  }
165
166
167
168  /**
169   * Reads data from this input stream.
170   *
171   * @param  b    The array into which the data should be read.
172   * @param  off  The position in the array at which point the data read may be
173   *              placed.
174   * @param  len  The maximum number of bytes that may be read into the
175   *              provided array.
176   *
177   * @return  The number of bytes read from the input stream into the provided
178   *          array, or -1 if the end of the stream has been reached.
179   *
180   * @throws  IOException  If a problem has occurred while generating data for
181   *                       use by this input stream.
182   */
183  public int read(byte[] b, int off, int len)
184         throws IOException
185  {
186    if (closed)
187    {
188      return -1;
189    }
190    else if (ioException != null)
191    {
192      throw ioException;
193    }
194
195    if ((entryBytes == null || !entryBytes.hasRemaining())
196        && !getNextEntry())
197    {
198      closed = true;
199      return -1;
200    }
201
202    int bytesRead = Math.min(len, entryBytes.remaining());
203    entryBytes.get(b, off, bytesRead);
204    return bytesRead;
205  }
206
207
208
209  /** {@inheritDoc} */
210  public boolean writeEntry(TemplateEntry entry)
211         throws IOException, MakeLDIFException
212  {
213    while (! closed)
214    {
215      try
216      {
217        if (entryQueue.offer(entry, 500, TimeUnit.MILLISECONDS))
218        {
219          return true;
220        }
221      } catch (InterruptedException ie) {}
222    }
223
224    return false;
225  }
226
227
228
229  /** {@inheritDoc} */
230  public void closeEntryWriter()
231  {
232    allGenerated = true;
233  }
234
235
236
237  /**
238   * Sets the I/O exception that should be thrown on any subsequent calls to
239   * <CODE>available</CODE> or <CODE>read</CODE>.
240   *
241   * @param  ioException   The I/O exception that should be thrown.
242   */
243  void setIOException(IOException ioException)
244  {
245    this.ioException = ioException;
246  }
247
248
249
250  /**
251   * Retrieves the next entry and puts it in the entry byte buffer.
252   *
253   * @return  <CODE>true</CODE> if the next entry is available, or
254   *          <CODE>false</CODE> if there are no more entries or if the input
255   *          stream has been closed.
256   */
257  private boolean getNextEntry()
258  {
259    TemplateEntry entry = entryQueue.poll();
260    while (entry == null)
261    {
262      if (closed)
263      {
264        return false;
265      }
266      else if (allGenerated)
267      {
268        entry = entryQueue.poll();
269        if (entry == null)
270        {
271          return false;
272        }
273      }
274      else
275      {
276        try
277        {
278          entry = entryQueue.poll(500, TimeUnit.MILLISECONDS);
279        } catch (InterruptedException ie) {}
280      }
281    }
282
283    try
284    {
285      entryOutputStream.reset();
286      ldifWriter.writeTemplateEntry(entry);
287      ldifWriter.flush();
288      entryBytes = ByteBuffer.wrap(entryOutputStream.toByteArray());
289    }
290    catch (LDIFException le)
291    {
292      // This should never happen.
293      ioException = new IOException(le.getMessage());
294      return false;
295    }
296    catch (IOException ioe)
297    {
298      // Neither should this.
299      ioException = ioe;
300      return false;
301    }
302
303    return true;
304  }
305}
306