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 }