/* * 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 2007-2009 Sun Microsystems, Inc. * Portions copyright 2011-2013 ForgeRock AS */ package org.opends.server.replication.server; import static org.opends.messages.BackendMessages.*; import static org.opends.messages.JebMessages.NOTE_JEB_EXPORT_FINAL_STATUS; import static org.opends.messages.JebMessages.NOTE_JEB_EXPORT_PROGRESS_REPORT; import static org.opends.messages.ReplicationMessages.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.*; import org.opends.server.replication.protocol.LDAPUpdateMsg; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.*; import org.opends.messages.Message; import org.opends.server.admin.Configuration; import org.opends.server.admin.server.ServerManagementContext; import org.opends.server.admin.std.server.BackendCfg; import org.opends.server.admin.std.server.ReplicationServerCfg; import org.opends.server.admin.std.server.ReplicationSynchronizationProviderCfg; import org.opends.server.admin.std.server.RootCfg; import org.opends.server.admin.std.server.SynchronizationProviderCfg; import org.opends.server.api.Backend; import org.opends.server.api.SynchronizationProvider; import org.opends.server.backends.jeb.BackupManager; import org.opends.server.config.ConfigException; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyDNOperation; import org.opends.server.core.ModifyOperation; import org.opends.server.core.SearchOperation; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.replication.common.ChangeNumber; import org.opends.server.replication.plugin.MultimasterReplication; import org.opends.server.replication.plugin.ReplicationServerListener; import org.opends.server.replication.protocol.AddMsg; import org.opends.server.replication.protocol.DeleteMsg; import org.opends.server.replication.protocol.ModifyDNMsg; import org.opends.server.replication.protocol.ModifyMsg; import org.opends.server.replication.protocol.UpdateMsg; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.AttributeType; import org.opends.server.types.Attributes; import org.opends.server.types.BackupConfig; import org.opends.server.types.BackupDirectory; import org.opends.server.types.ByteString; import org.opends.server.types.CanceledOperationException; import org.opends.server.types.ConditionResult; import org.opends.server.types.Control; import org.opends.server.types.DN; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.DirectoryException; import org.opends.server.types.DereferencePolicy; import org.opends.server.types.Entry; import org.opends.server.types.FilterType; import org.opends.server.types.IndexType; import org.opends.server.types.InitializationException; import org.opends.server.types.LDIFExportConfig; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.LDIFImportResult; import org.opends.server.types.RawAttribute; import org.opends.server.types.RestoreConfig; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchScope; import org.opends.server.types.SearchResultEntry; import org.opends.server.types.ObjectClass; import org.opends.server.util.AddChangeRecordEntry; import org.opends.server.util.DeleteChangeRecordEntry; import org.opends.server.util.LDIFReader; import org.opends.server.util.LDIFWriter; import org.opends.server.util.ModifyChangeRecordEntry; import org.opends.server.util.ModifyDNChangeRecordEntry; import org.opends.server.util.Validator; import static org.opends.server.config.ConfigConstants.ATTR_OBJECTCLASSES_LC; import org.opends.server.protocols.internal.InternalSearchOperation; import static org.opends.server.util.ServerConstants.*; /** * This class defines a backend that stores its information in an * associated replication server object. * This is primarily intended to take advantage of the backup/restore/ * import/export of the backend API, and to provide an LDAP access * to the replication server database. * <BR><BR> * Entries stored in this backend are held in the DB associated with * the replication server. * <BR><BR> * Currently are only implemented the create and restore backup features. * */ public class ReplicationBackend extends Backend { private static final String CHANGE_NUMBER = "replicationChangeNumber"; /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); private static final String BASE_DN = "dc=replicationchanges"; // The base DNs for this backend. private DN[] baseDNs; // The base DNs for this backend, in a hash set. private HashSet<DN> baseDNSet; // The set of supported controls for this backend. private HashSet<String> supportedControls; // The set of supported features for this backend. private HashSet<String> supportedFeatures; private ReplicationServer server; /** * The number of milliseconds between job progress reports. */ private long progressInterval = 10000; /** * The current number of entries exported. */ private long exportedCount = 0; /** * The current number of entries skipped. */ private long skippedCount = 0; //Objectclass for getEntry root entries. private HashMap<ObjectClass,String> rootObjectclasses; //Attributes used for getEntry root entries. private LinkedHashMap<AttributeType,List<Attribute>> attributes; //Operational attributes used for getEntry root entries. private Map<AttributeType,List<Attribute>> operationalAttributes; /** * Creates a new backend with the provided information. All backend * implementations must implement a default constructor that use * <CODE>super()</CODE> to invoke this constructor. */ public ReplicationBackend() { super(); // Perform all initialization in initializeBackend. } /** * Set the base DNs for this backend. This is used by the unit tests * to set the base DNs without having to provide a configuration * object when initializing the backend. * @param baseDNs The set of base DNs to be served by this memory backend. */ public void setBaseDNs(DN[] baseDNs) { this.baseDNs = baseDNs; } /** * {@inheritDoc} */ @Override() public void configureBackend(Configuration config) throws ConfigException { if (config != null) { Validator.ensureTrue(config instanceof BackendCfg); BackendCfg cfg = (BackendCfg) config; DN[] newBaseDNs = new DN[cfg.getBaseDN().size()]; cfg.getBaseDN().toArray(newBaseDNs); setBaseDNs(newBaseDNs); } } /** * {@inheritDoc} */ @Override() public synchronized void initializeBackend() throws ConfigException, InitializationException { if ((baseDNs == null) || (baseDNs.length != 1)) { Message message = ERR_MEMORYBACKEND_REQUIRE_EXACTLY_ONE_BASE.get(); throw new ConfigException(message); } baseDNSet = new HashSet<DN>(); baseDNSet.addAll(Arrays.asList(baseDNs)); supportedControls = new HashSet<String>(); supportedFeatures = new HashSet<String>(); for (DN dn : baseDNs) { try { DirectoryServer.registerBaseDN(dn, this, true); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( dn.toString(), getExceptionMessage(e)); throw new InitializationException(message, e); } } rootObjectclasses = new LinkedHashMap<ObjectClass,String>(3); rootObjectclasses.put(DirectoryServer.getTopObjectClass(), OC_TOP); ObjectClass domainOC = DirectoryServer.getObjectClass("domain", true); rootObjectclasses.put(domainOC, "domain"); ObjectClass objectclassOC = DirectoryServer.getObjectClass(ATTR_OBJECTCLASSES_LC, true); rootObjectclasses.put(objectclassOC, ATTR_OBJECTCLASSES_LC); attributes = new LinkedHashMap<AttributeType,List<Attribute>>(); Attribute a = Attributes.create("changetype", "add"); ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); attrList.add(a); attributes.put(a.getAttributeType(), attrList); operationalAttributes = new LinkedHashMap<AttributeType,List<Attribute>>(); } /** * {@inheritDoc} */ @Override() public synchronized void finalizeBackend() { for (DN dn : baseDNs) { try { DirectoryServer.deregisterBaseDN(dn); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } /** * {@inheritDoc} */ @Override() public DN[] getBaseDNs() { return baseDNs; } /** * {@inheritDoc} */ @Override() public synchronized long getEntryCount() { if (server==null) { try { server = getReplicationServer(); if (server == null) { return 0; } } catch(Exception e) { return 0; } } //This method only returns the number of actual change entries, the //domain and any baseDN entries are not counted. long retNum=0; Iterator<ReplicationServerDomain> rcachei = server.getDomainIterator(); if (rcachei != null) { while (rcachei.hasNext()) { ReplicationServerDomain rsd = rcachei.next(); retNum += rsd.getChangesCount(); } } return retNum; } /** * {@inheritDoc} */ @Override() public boolean isLocal() { return true; } /** * {@inheritDoc} */ @Override() public boolean isIndexed(AttributeType attributeType, IndexType indexType) { return true; } /** * {@inheritDoc} */ @Override() public synchronized Entry getEntry(DN entryDN) { Entry e = null; try { if(baseDNSet.contains(entryDN)) { return new Entry(entryDN, rootObjectclasses, attributes, operationalAttributes); } else { InternalClientConnection conn = InternalClientConnection.getRootConnection(); SearchFilter filter= SearchFilter.createFilterFromString("(changetype=*)"); InternalSearchOperation searchOperation = new InternalSearchOperation(conn, InternalClientConnection.nextOperationID(), InternalClientConnection.nextMessageID(), null, entryDN, SearchScope.BASE_OBJECT, DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, filter, null, null); search(searchOperation); LinkedList<SearchResultEntry> resultEntries = searchOperation.getSearchEntries(); if(resultEntries.size() != 0) { e=resultEntries.getFirst(); } } } catch (DirectoryException ex) { e=null; } return e; } /** * {@inheritDoc} */ @Override() public synchronized boolean entryExists(DN entryDN) { return getEntry(entryDN) != null; } /** * {@inheritDoc} */ @Override() public synchronized void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException { Message message = ERR_BACKUP_ADD_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } /** * {@inheritDoc} */ @Override() public synchronized void deleteEntry(DN entryDN, DeleteOperation deleteOperation) throws DirectoryException { Message message = ERR_BACKUP_DELETE_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } /** * {@inheritDoc} */ @Override() public synchronized void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation) throws DirectoryException { Message message = ERR_BACKUP_MODIFY_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } /** * {@inheritDoc} */ @Override() public synchronized void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) throws DirectoryException { Message message = ERR_BACKUP_MODIFY_DN_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } /** * {@inheritDoc} */ @Override() public HashSet<String> getSupportedControls() { return supportedControls; } /** * {@inheritDoc} */ @Override() public HashSet<String> getSupportedFeatures() { return supportedFeatures; } /** * {@inheritDoc} */ @Override() public boolean supportsLDIFExport() { return true; } /** * {@inheritDoc} */ @Override() public synchronized void exportLDIF(LDIFExportConfig exportConfig) throws DirectoryException { List<DN> includeBranches = exportConfig.getIncludeBranches(); DN baseDN; ArrayList<ReplicationServerDomain> exportContainers = new ArrayList<ReplicationServerDomain>(); if(server == null) { Message message = ERR_REPLICATONBACKEND_EXPORT_LDIF_FAILED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,message); } Iterator<ReplicationServerDomain> rsdi = server.getDomainIterator(); if (rsdi != null) { while (rsdi.hasNext()) { ReplicationServerDomain rc = rsdi.next(); // Skip containers that are not covered by the include branches. baseDN = DN.decode(rc.getBaseDn() + "," + BASE_DN); if (includeBranches == null || includeBranches.isEmpty()) { exportContainers.add(rc); } else { for (DN includeBranch : includeBranches) { if (includeBranch.isDescendantOf(baseDN) || includeBranch.isAncestorOf(baseDN)) { exportContainers.add(rc); } } } } } // Make a note of the time we started. long startTime = System.currentTimeMillis(); // Start a timer for the progress report. Timer timer = new Timer(); TimerTask progressTask = new ProgressTask(); timer.scheduleAtFixedRate(progressTask, progressInterval, progressInterval); // Create the LDIF writer. LDIFWriter ldifWriter; try { ldifWriter = new LDIFWriter(exportConfig); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message = ERR_BACKEND_CANNOT_CREATE_LDIF_WRITER.get(String.valueOf(e)); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); } exportRootChanges(exportContainers, exportConfig, ldifWriter); // Iterate through the containers. try { for (ReplicationServerDomain exportContainer : exportContainers) { if (exportConfig.isCancelled()) { break; } processContainer(exportContainer, exportConfig, ldifWriter, null); } } finally { timer.cancel(); // Close the LDIF writer try { ldifWriter.close(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } long finishTime = System.currentTimeMillis(); long totalTime = (finishTime - startTime); float rate = 0; if (totalTime > 0) { rate = 1000f*exportedCount / totalTime; } Message message = NOTE_JEB_EXPORT_FINAL_STATUS.get( exportedCount, skippedCount, totalTime/1000, rate); logError(message); } /* * Exports the root changes of the export, and one entry by domain. */ private void exportRootChanges(List<ReplicationServerDomain> exportContainers, LDIFExportConfig exportConfig, LDIFWriter ldifWriter) { Map<AttributeType,List<Attribute>> attrs = new HashMap<AttributeType,List<Attribute>>(); ArrayList<Attribute> ldapAttrList = new ArrayList<Attribute>(); AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); AttributeBuilder builder = new AttributeBuilder(ocType); builder.add("top"); builder.add("domain"); Attribute ocAttr = builder.toAttribute(); ldapAttrList.add(ocAttr); attrs.put(ocType, ldapAttrList); try { AddChangeRecordEntry changeRecord = new AddChangeRecordEntry(DN.decode(BASE_DN), attrs); ldifWriter.writeChangeRecord(changeRecord); } catch (Exception e) { /* do nothing */ } for (ReplicationServerDomain exportContainer : exportContainers) { if (exportConfig != null && exportConfig.isCancelled()) { break; } attrs.clear(); ldapAttrList.clear(); ldapAttrList.add(ocAttr); attrs.put(ocType, ldapAttrList); TRACER.debugInfo("State=" + exportContainer.getDbServerState().toString()); Attribute stateAttr = Attributes.create("state", exportContainer .getDbServerState().toString()); ldapAttrList.clear(); ldapAttrList.add(stateAttr); attrs.put(stateAttr.getAttributeType(), ldapAttrList); Attribute genidAttr = Attributes.create("generation-id", String .valueOf(exportContainer.getGenerationId()) + exportContainer.getBaseDn()); ldapAttrList.clear(); ldapAttrList.add(genidAttr); attrs.put(genidAttr.getAttributeType(), ldapAttrList); try { AddChangeRecordEntry changeRecord = new AddChangeRecordEntry(DN.decode( exportContainer.getBaseDn() + "," + BASE_DN), attrs); ldifWriter.writeChangeRecord(changeRecord); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message = ERR_BACKEND_EXPORT_ENTRY.get( exportContainer.getBaseDn() + "," + BASE_DN, String.valueOf(e)); logError(message); } } } /** * Processes the changes for a given ReplicationServerDomain. */ private void processContainer(ReplicationServerDomain rsd, LDIFExportConfig exportConfig, LDIFWriter ldifWriter, SearchOperation searchOperation) { // Walk through the servers for (int serverId : rsd.getServers()) { if (exportConfig != null && exportConfig.isCancelled()) { // Abort if cancelled break; } ChangeNumber previousChangeNumber = null; if (searchOperation != null) { // Try to optimize for filters like replicationChangeNumber>=xxxxx // or replicationChangeNumber=xxxxx : // If the search filter is one of these 2 filters, move directly to // ChangeNumber=xxxx before starting the iteration. SearchFilter filter = searchOperation.getFilter(); previousChangeNumber = extractChangeNumber(filter); if ((previousChangeNumber == null) && (filter.getFilterType().equals(FilterType.AND))) { for (SearchFilter filterComponents: filter.getFilterComponents()) { previousChangeNumber = extractChangeNumber(filterComponents); if (previousChangeNumber != null) break; } } } ReplicationIterator ri = rsd.getChangelogIterator(serverId, previousChangeNumber); if (ri != null) { try { int lookthroughCount = 0; int lookthroughLimit = 0; if (searchOperation != null) { lookthroughLimit = searchOperation.getClientConnection().getLookthroughLimit(); } // Walk through the changes while (ri.getChange() != null) { if (exportConfig != null && exportConfig.isCancelled()) { // abort if cancelled break; } if (searchOperation != null) { try { if (lookthroughLimit > 0 && lookthroughCount > lookthroughLimit) { // Lookthrough limit exceeded searchOperation.setResultCode( ResultCode.ADMIN_LIMIT_EXCEEDED); searchOperation.setErrorMessage(null); break; } searchOperation.checkIfCanceled(false); } catch (CanceledOperationException e) { searchOperation.setResultCode(ResultCode.CANCELED); searchOperation.setErrorMessage(null); break; } } lookthroughCount++; UpdateMsg msg = ri.getChange(); processChange( msg, exportConfig, ldifWriter, searchOperation, rsd.getBaseDn()); if (!ri.next()) break; } } finally { ri.releaseCursor(); } } } } /** * Attempt to extract a ChangeNumber from searchFilter like * ReplicationChangeNumber=xxxx or ReplicationChangeNumber>=xxxx. * * @param filter The filter to evaluate. * * @return The extracted ChangeNumber or null if no ChangeNumber * was found. */ private ChangeNumber extractChangeNumber(SearchFilter filter) { AttributeType changeNumberAttrType = DirectoryServer.getDefaultAttributeType(CHANGE_NUMBER); FilterType filterType = filter.getFilterType(); if ( (filterType.equals(FilterType.GREATER_OR_EQUAL) || filterType.equals(FilterType.EQUALITY) ) && (filter.getAttributeType().equals(changeNumberAttrType))) { try { ChangeNumber startingChangeNumber = new ChangeNumber(filter.getAssertionValue().getValue().toString()); return new ChangeNumber( startingChangeNumber.getTime(), startingChangeNumber.getSeqnum()-1, startingChangeNumber.getServerId()); } catch (Exception e) { // don't try to optimize the search if we the ChangeNumber is // not a valid replication ChangeNumber. } } return null; } /** * Export one change. */ private void processChange(UpdateMsg updateMsg, LDIFExportConfig exportConfig, LDIFWriter ldifWriter, SearchOperation searchOperation, String baseDN) { InternalClientConnection conn = InternalClientConnection.getRootConnection(); Entry entry = null; DN dn = null; ObjectClass objectclass = DirectoryServer.getDefaultObjectClass("extensibleObject"); try { if (updateMsg instanceof LDAPUpdateMsg) { LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg; if (msg instanceof AddMsg) { AddMsg addMsg = (AddMsg)msg; AddOperation addOperation = (AddOperation)msg.createOperation(conn); dn = DN.decode("puid=" + addMsg.getParentEntryUUID() + "+" + CHANGE_NUMBER + "=" + msg.getChangeNumber().toString() + "+" + msg.getDn() + "," + BASE_DN); Map<AttributeType,List<Attribute>> attrs = new HashMap<AttributeType,List<Attribute>>(); Map<ObjectClass, String> objectclasses = new HashMap<ObjectClass, String>(); for (RawAttribute a : addOperation.getRawAttributes()) { Attribute attr = a.toAttribute(); if (attr.getAttributeType().isObjectClassType()) { for (ByteString os : a.getValues()) { String ocName = os.toString(); ObjectClass oc = DirectoryServer.getObjectClass(toLowerCase(ocName)); if (oc == null) { oc = DirectoryServer.getDefaultObjectClass(ocName); } objectclasses.put(oc,ocName); } } else { addAttribute(attrs, attr); } } Attribute changetype = Attributes.create("changetype", "add"); addAttribute(attrs, changetype); if (exportConfig != null) { AddChangeRecordEntry changeRecord = new AddChangeRecordEntry(dn, attrs); ldifWriter.writeChangeRecord(changeRecord); } else { entry = new Entry(dn, objectclasses, attrs, null); } } else if (msg instanceof DeleteMsg) { DeleteMsg delMsg = (DeleteMsg)msg; dn = DN.decode("uuid=" + msg.getEntryUUID() + "," + CHANGE_NUMBER + "=" + delMsg.getChangeNumber().toString()+ "," + msg.getDn() +","+ BASE_DN); DeleteChangeRecordEntry changeRecord = new DeleteChangeRecordEntry(dn); if (exportConfig != null) { ldifWriter.writeChangeRecord(changeRecord); } else { Writer writer = new Writer(); LDIFWriter ldifWriter2 = writer.getLDIFWriter(); ldifWriter2.writeChangeRecord(changeRecord); LDIFReader reader = writer.getLDIFReader(); entry = reader.readEntry(); } } else if (msg instanceof ModifyMsg) { ModifyOperation op = (ModifyOperation)msg.createOperation(conn); dn = DN.decode("uuid=" + msg.getEntryUUID() + "," + CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + msg.getDn() +","+ BASE_DN); op.setInternalOperation(true); ModifyChangeRecordEntry changeRecord = new ModifyChangeRecordEntry(dn, op.getRawModifications()); if (exportConfig != null) { ldifWriter.writeChangeRecord(changeRecord); } else { Writer writer = new Writer(); LDIFWriter ldifWriter2 = writer.getLDIFWriter(); ldifWriter2.writeChangeRecord(changeRecord); LDIFReader reader = writer.getLDIFReader(); entry = reader.readEntry(); } } else if (msg instanceof ModifyDNMsg) { ModifyDNOperation op = (ModifyDNOperation)msg.createOperation(conn); dn = DN.decode("uuid=" + msg.getEntryUUID() + "," + CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + msg.getDn() +","+ BASE_DN); op.setInternalOperation(true); ModifyDNChangeRecordEntry changeRecord = new ModifyDNChangeRecordEntry(dn, op.getNewRDN(), op.deleteOldRDN(), op.getNewSuperior()); if (exportConfig != null) { ldifWriter.writeChangeRecord(changeRecord); } else { Writer writer = new Writer(); LDIFWriter ldifWriter2 = writer.getLDIFWriter(); ldifWriter2.writeChangeRecord(changeRecord); LDIFReader reader = writer.getLDIFReader(); entry = reader.readEntry(); } } if (exportConfig != null) { this.exportedCount++; } else { // Add extensibleObject objectclass and the ChangeNumber // in the entry. if (!entry.getObjectClasses().containsKey(objectclass)) entry.addObjectClass(objectclass); Attribute changeNumber = Attributes.create(CHANGE_NUMBER, msg.getChangeNumber().toString()); addAttribute(entry.getUserAttributes(), changeNumber); Attribute domain = Attributes.create("replicationDomain", baseDN); addAttribute(entry.getUserAttributes(), domain); // Get the base DN, scope, and filter for the search. DN searchBaseDN = searchOperation.getBaseDN(); SearchScope scope = searchOperation.getScope(); SearchFilter filter = searchOperation.getFilter(); boolean ms = entry.matchesBaseAndScope(searchBaseDN, scope); boolean mf = filter.matchesEntry(entry); if ( ms && mf ) { searchOperation.returnEntry(entry, new LinkedList<Control>()); } } } } catch (Exception e) { this.skippedCount++; if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message; String dnStr; if (dn == null) { dnStr = "Unkown"; } else { dnStr = dn.toNormalizedString(); } if (exportConfig != null) { message = ERR_BACKEND_EXPORT_ENTRY.get( dnStr, String.valueOf(e)); } else { message = ERR_BACKEND_SEARCH_ENTRY.get( dnStr, e.getLocalizedMessage()); } logError(message); } } /** * Add an attribute to a provided Map of attribute. * * @param attributes The Map that should be updated. * @param attribute The attribute that should be added to the Map. */ private void addAttribute( Map<AttributeType,List<Attribute>> attributes, Attribute attribute) { AttributeType attrType = attribute.getAttributeType(); List<Attribute> attrs = attributes.get(attrType); if (attrs == null) { attrs = new ArrayList<Attribute>(1); attrs.add(attribute); attributes.put(attrType, attrs); } else { attrs.add(attribute); } } /** * {@inheritDoc} */ @Override() public boolean supportsLDIFImport() { return false; } /** * {@inheritDoc} */ @Override() public synchronized LDIFImportResult importLDIF(LDIFImportConfig importConfig) throws DirectoryException { Message message = ERR_REPLICATONBACKEND_IMPORT_LDIF_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } /** * {@inheritDoc} */ @Override() public boolean supportsBackup() { // This backend does not provide a backup/restore mechanism. return true; } /** * {@inheritDoc} */ @Override() public boolean supportsBackup(BackupConfig backupConfig, StringBuilder unsupportedReason) { return true; } /** * {@inheritDoc} */ @Override() public void createBackup(BackupConfig backupConfig) throws DirectoryException { BackupManager backupManager = new BackupManager(getBackendID()); File backendDir = getFileForPath(getReplicationServerCfg() .getReplicationDBDirectory()); backupManager.createBackup(backendDir, backupConfig); } /** * {@inheritDoc} */ @Override() public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException { BackupManager backupManager = new BackupManager(getBackendID()); backupManager.removeBackup(backupDirectory, backupID); } /** * {@inheritDoc} */ @Override() public boolean supportsRestore() { return true; } /** * {@inheritDoc} */ @Override() public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException { BackupManager backupManager = new BackupManager(getBackendID()); File backendDir = getFileForPath(getReplicationServerCfg() .getReplicationDBDirectory()); backupManager.restoreBackup(backendDir, restoreConfig); } /** * {@inheritDoc} */ @Override() public long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); } /** * {@inheritDoc} */ @Override() public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_HAS_SUBORDINATES_NOT_SUPPORTED.get()); } /** * Set the replication server associated with this backend. * @param server The replication server. */ public void setServer(ReplicationServer server) { this.server = server; } /** * This class reports progress of the export job at fixed intervals. */ private final class ProgressTask extends TimerTask { /** * The number of entries that had been exported at the time of the * previous progress report. */ private long previousCount = 0; /** * The time in milliseconds of the previous progress report. */ private long previousTime; /** * Create a new export progress task. */ public ProgressTask() { previousTime = System.currentTimeMillis(); } /** * The action to be performed by this timer task. */ @Override public void run() { long latestCount = exportedCount; long deltaCount = (latestCount - previousCount); long latestTime = System.currentTimeMillis(); long deltaTime = latestTime - previousTime; if (deltaTime == 0) { return; } float rate = 1000f*deltaCount / deltaTime; Message message = NOTE_JEB_EXPORT_PROGRESS_REPORT.get(latestCount, skippedCount, rate); logError(message); previousCount = latestCount; previousTime = latestTime; } } /** * {@inheritDoc} */ @Override() public synchronized void search(SearchOperation searchOperation) throws DirectoryException { // Get the base DN, scope, and filter for the search. DN searchBaseDN = searchOperation.getBaseDN(); DN baseDN; ArrayList<ReplicationServerDomain> searchContainers = new ArrayList<ReplicationServerDomain>(); //This check is for GroupManager initialization. It currently doesn't //come into play because the replication server variable is null in //the check above. But if the order of initialization of the server variable //is ever changed, the following check will keep replication change entries //from being added to the groupmanager cache erroneously. List<Control> requestControls = searchOperation.getRequestControls(); if (requestControls != null) { for (Control c : requestControls) { if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE)) { return; } } } // don't do anything if the search is a base search on // the backend suffix. try { DN backendBaseDN = DN.decode(BASE_DN); if ( (searchOperation.getScope().equals(SearchScope.BASE_OBJECT)) && (backendBaseDN.equals(searchOperation.getBaseDN())) ) { return; } } catch (Exception e) { return; } // Make sure the base entry exists if it's supposed to be in this backend. if (!handlesEntry(searchBaseDN)) { DN matchedDN = searchBaseDN.getParentDNInSuffix(); while (matchedDN != null) { if (handlesEntry(matchedDN)) { break; } matchedDN = matchedDN.getParentDNInSuffix(); } Message message = ERR_REPLICATIONBACKEND_ENTRY_DOESNT_EXIST. get(String.valueOf(searchBaseDN)); throw new DirectoryException( ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (server==null) { server = getReplicationServer(); if (server == null) { if (baseDNSet.contains(searchBaseDN)) { return; } else { Message message = ERR_REPLICATIONBACKEND_ENTRY_DOESNT_EXIST. get(String.valueOf(searchBaseDN)); throw new DirectoryException( ResultCode.NO_SUCH_OBJECT, message, null, null); } } } // Walk through all entries and send the ones that match. Iterator<ReplicationServerDomain> rsdi = server.getDomainIterator(); if (rsdi != null) { while (rsdi.hasNext()) { ReplicationServerDomain rsd = rsdi.next(); // Skip containers that are not covered by the include branches. baseDN = DN.decode(rsd.getBaseDn() + "," + BASE_DN); if (searchBaseDN.isDescendantOf(baseDN) || searchBaseDN.isAncestorOf(baseDN)) { searchContainers.add(rsd); } } } for (ReplicationServerDomain exportContainer : searchContainers) { processContainer(exportContainer, null, null, searchOperation); } } /** * Retrieves the replication server associated to this backend. * * @return The server retrieved * @throws DirectoryException When it occurs. */ private ReplicationServer getReplicationServer() throws DirectoryException { ReplicationServer replicationServer = null; DirectoryServer.getSynchronizationProviders(); for (SynchronizationProvider<?> provider : DirectoryServer.getSynchronizationProviders()) { if (provider instanceof MultimasterReplication) { MultimasterReplication mmp = (MultimasterReplication)provider; ReplicationServerListener list = mmp.getReplicationServerListener(); if (list != null) { replicationServer = list.getReplicationServer(); break; } } } return replicationServer; } // Find the replication server configuration associated with this // replication backend. private ReplicationServerCfg getReplicationServerCfg() throws DirectoryException { RootCfg root = ServerManagementContext.getInstance().getRootConfiguration(); for (String name : root.listSynchronizationProviders()) { SynchronizationProviderCfg syncCfg; try { syncCfg = root.getSynchronizationProvider(name); } catch (ConfigException e) { throw new DirectoryException(ResultCode.OPERATIONS_ERROR, ERR_REPLICATION_SERVER_CONFIG_NOT_FOUND.get(), e); } if (syncCfg instanceof ReplicationSynchronizationProviderCfg) { ReplicationSynchronizationProviderCfg scfg = (ReplicationSynchronizationProviderCfg) syncCfg; try { return scfg.getReplicationServer(); } catch (ConfigException e) { throw new DirectoryException(ResultCode.OPERATIONS_ERROR, ERR_REPLICATION_SERVER_CONFIG_NOT_FOUND.get(), e); } } } // No replication server found. throw new DirectoryException(ResultCode.OPERATIONS_ERROR, ERR_REPLICATION_SERVER_CONFIG_NOT_FOUND.get()); } /** * Writer class to read/write from/to a bytearray. */ private static final class Writer { // The underlying output stream. private final ByteArrayOutputStream stream; // The underlying LDIF config. private final LDIFExportConfig config; // The LDIF writer. private final LDIFWriter writer; /** * Create a new string writer. */ public Writer() { this.stream = new ByteArrayOutputStream(); this.config = new LDIFExportConfig(stream); try { this.writer = new LDIFWriter(config); } catch (IOException e) { // Should not happen. throw new RuntimeException(e); } } /** * Get the LDIF writer. * * @return Returns the LDIF writer. */ public LDIFWriter getLDIFWriter() { return writer; } /** * Close the writer and get an LDIF reader for the LDIF content. * * @return Returns an LDIF Reader. * @throws Exception * If an error occurred closing the writer. */ public LDIFReader getLDIFReader() throws Exception { writer.close(); String ldif = stream.toString("UTF-8"); ldif = ldif.replace("\n-\n", "\n"); ByteArrayInputStream istream = new ByteArrayInputStream(ldif.getBytes()); LDIFImportConfig newConfig = new LDIFImportConfig(istream); // ReplicationBackend may contain entries that are not schema // compliant. Let's ignore them for now. newConfig.setValidateSchema(false); return new LDIFReader(newConfig); } } /** * {@inheritDoc} */ @Override public void preloadEntryCache() throws UnsupportedOperationException { throw new UnsupportedOperationException("Operation not supported."); } }