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 }