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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.backends.jeb;
028    import org.opends.messages.Message;
029    
030    import com.sleepycat.je.Cursor;
031    import com.sleepycat.je.CursorConfig;
032    import com.sleepycat.je.DatabaseEntry;
033    import com.sleepycat.je.DatabaseException;
034    import com.sleepycat.je.LockMode;
035    import com.sleepycat.je.OperationStatus;
036    
037    import org.opends.server.types.DN;
038    import org.opends.server.types.Entry;
039    import org.opends.server.types.LDIFExportConfig;
040    import org.opends.server.util.LDIFException;
041    import org.opends.server.util.StaticUtils;
042    
043    import java.io.IOException;
044    import java.util.*;
045    
046    import org.opends.server.types.DebugLogLevel;
047    import static org.opends.server.loggers.ErrorLogger.logError;
048    import static org.opends.server.loggers.debug.DebugLogger.*;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import static org.opends.messages.JebMessages.*;
051    
052    /**
053     * Export a JE backend to LDIF.
054     */
055    public class ExportJob
056    {
057      /**
058       * The tracer object for the debug logger.
059       */
060      private static final DebugTracer TRACER = getTracer();
061    
062    
063      /**
064       * The requested LDIF export configuration.
065       */
066      private LDIFExportConfig exportConfig;
067    
068      /**
069       * The number of milliseconds between job progress reports.
070       */
071      private long progressInterval = 10000;
072    
073      /**
074       * The current number of entries exported.
075       */
076      private long exportedCount = 0;
077    
078      /**
079       * The current number of entries skipped.
080       */
081      private long skippedCount = 0;
082    
083      /**
084       * Create a new export job.
085       *
086       * @param exportConfig The requested LDIF export configuration.
087       */
088      public ExportJob(LDIFExportConfig exportConfig)
089      {
090        this.exportConfig = exportConfig;
091      }
092    
093      /**
094       * Export entries from the backend to an LDIF file.
095       * @param rootContainer The root container to export.
096       * @throws DatabaseException If an error occurs in the JE database.
097       * @throws IOException If an I/O error occurs while writing an entry.
098       * @throws JebException If an error occurs in the JE backend.
099       * @throws LDIFException If an error occurs while trying to determine whether
100       * to write an entry.
101       */
102      public void exportLDIF(RootContainer rootContainer)
103           throws IOException, LDIFException, DatabaseException, JebException
104      {
105        List<DN> includeBranches = exportConfig.getIncludeBranches();
106        DN baseDN;
107        ArrayList<EntryContainer> exportContainers =
108            new ArrayList<EntryContainer>();
109    
110        for (EntryContainer entryContainer : rootContainer.getEntryContainers())
111        {
112          // Skip containers that are not covered by the include branches.
113          baseDN = entryContainer.getBaseDN();
114    
115          if (includeBranches == null || includeBranches.isEmpty())
116          {
117            exportContainers.add(entryContainer);
118          }
119          else
120          {
121            for (DN includeBranch : includeBranches)
122            {
123              if (includeBranch.isDescendantOf(baseDN) ||
124                   includeBranch.isAncestorOf(baseDN))
125              {
126                exportContainers.add(entryContainer);
127              }
128            }
129          }
130        }
131    
132        // Make a note of the time we started.
133        long startTime = System.currentTimeMillis();
134    
135        // Start a timer for the progress report.
136        Timer timer = new Timer();
137        TimerTask progressTask = new ProgressTask();
138        timer.scheduleAtFixedRate(progressTask, progressInterval,
139                                  progressInterval);
140    
141        // Iterate through the containers.
142        try
143        {
144          for (EntryContainer exportContainer : exportContainers)
145          {
146            if (exportConfig.isCancelled())
147            {
148              break;
149            }
150    
151            exportContainer.sharedLock.lock();
152            try
153            {
154              exportContainer(exportContainer);
155            }
156            finally
157            {
158              exportContainer.sharedLock.unlock();
159            }
160          }
161        }
162        finally
163        {
164          timer.cancel();
165        }
166    
167    
168        long finishTime = System.currentTimeMillis();
169        long totalTime = (finishTime - startTime);
170    
171        float rate = 0;
172        if (totalTime > 0)
173        {
174          rate = 1000f*exportedCount / totalTime;
175        }
176    
177        Message message = NOTE_JEB_EXPORT_FINAL_STATUS.get(
178            exportedCount, skippedCount, totalTime/1000, rate);
179        logError(message);
180    
181      }
182    
183      /**
184       * Export the entries in a single entry entryContainer, in other words from
185       * one of the base DNs.
186       * @param entryContainer The entry container that holds the entries to be
187       *                       exported.
188       * @throws DatabaseException If an error occurs in the JE database.
189       * @throws IOException If an error occurs while writing an entry.
190       * @throws  LDIFException  If an error occurs while trying to determine
191       *                         whether to write an entry.
192       */
193      private void exportContainer(EntryContainer entryContainer)
194           throws DatabaseException, IOException, LDIFException
195      {
196        ID2Entry id2entry = entryContainer.getID2Entry();
197    
198        Cursor cursor = id2entry.openCursor(null, new CursorConfig());
199        try
200        {
201          DatabaseEntry key = new DatabaseEntry();
202          DatabaseEntry data = new DatabaseEntry();
203    
204          OperationStatus status;
205          for (status = cursor.getFirst(key, data, LockMode.DEFAULT);
206               status == OperationStatus.SUCCESS;
207               status = cursor.getNext(key, data, LockMode.DEFAULT))
208          {
209            if (exportConfig.isCancelled())
210            {
211              break;
212            }
213    
214            EntryID entryID = null;
215            try
216            {
217              entryID = new EntryID(key);
218            }
219            catch (Exception e)
220            {
221              if (debugEnabled())
222              {
223                TRACER.debugCaught(DebugLogLevel.ERROR, e);
224    
225                TRACER.debugError("Malformed id2entry ID %s.%n",
226                                StaticUtils.bytesToHex(key.getData()));
227              }
228              skippedCount++;
229              continue;
230            }
231    
232            if (entryID.longValue() == 0)
233            {
234              // This is the stored entry count.
235              continue;
236            }
237    
238            Entry entry = null;
239            try
240            {
241              entry = JebFormat.entryFromDatabase(data.getData(),
242                           entryContainer.getRootContainer().getCompressedSchema());
243            }
244            catch (Exception e)
245            {
246              if (debugEnabled())
247              {
248                TRACER.debugCaught(DebugLogLevel.ERROR, e);
249    
250                TRACER.debugError("Malformed id2entry record for ID %d:%n%s%n",
251                           entryID.longValue(),
252                           StaticUtils.bytesToHex(data.getData()));
253              }
254              skippedCount++;
255              continue;
256            }
257    
258            if (entry.toLDIF(exportConfig))
259            {
260              exportedCount++;
261            }
262            else
263            {
264              skippedCount++;
265            }
266          }
267        }
268        finally
269        {
270          cursor.close();
271        }
272      }
273    
274      /**
275       * This class reports progress of the export job at fixed intervals.
276       */
277      class ProgressTask extends TimerTask
278      {
279        /**
280         * The number of entries that had been exported at the time of the
281         * previous progress report.
282         */
283        private long previousCount = 0;
284    
285        /**
286         * The time in milliseconds of the previous progress report.
287         */
288        private long previousTime;
289    
290        /**
291         * Create a new export progress task.
292         */
293        public ProgressTask()
294        {
295          previousTime = System.currentTimeMillis();
296        }
297    
298        /**
299         * The action to be performed by this timer task.
300         */
301        public void run()
302        {
303          long latestCount = exportedCount;
304          long deltaCount = (latestCount - previousCount);
305          long latestTime = System.currentTimeMillis();
306          long deltaTime = latestTime - previousTime;
307    
308          if (deltaTime == 0)
309          {
310            return;
311          }
312    
313          float rate = 1000f*deltaCount / deltaTime;
314    
315          Message message =
316              NOTE_JEB_EXPORT_PROGRESS_REPORT.get(latestCount, skippedCount, rate);
317          logError(message);
318    
319          previousCount = latestCount;
320          previousTime = latestTime;
321        }
322      };
323    
324    }