| | |
| | | |
| | | ReplicationServer rs = eclwe.getReplicationServer(); |
| | | rs.disableEligibility(excludedDomains); |
| | | int[] limits = rs.getECLDraftCNLimits( |
| | | int[] limits = rs.getECLChangeNumberLimits( |
| | | rs.getEligibleCSN(), excludedDomains); |
| | | |
| | | first = String.valueOf(limits[0]); |
| | |
| | | |
| | | ReplicationServer rs = eclwe.getReplicationServer(); |
| | | rs.disableEligibility(excludedDomains); |
| | | int[] limits = rs.getECLDraftCNLimits( |
| | | int[] limits = rs.getECLChangeNumberLimits( |
| | | rs.getEligibleCSN(), excludedDomains); |
| | | |
| | | last = String.valueOf(limits[1]); |
| | |
| | | private MultiDomainServerState cookie; |
| | | |
| | | /** The changeNumber as specified by draft-good-ldap-changelog. */ |
| | | private int draftChangeNumber; |
| | | private int changeNumber; |
| | | |
| | | /** |
| | | * Creates a new message. |
| | | * @param updateMsg The provided update message. |
| | | * @param cookie The provided cookie value |
| | | * @param baseDN The provided baseDN. |
| | | * @param draftChangeNumber The provided draft change number. |
| | | * @param changeNumber The provided change number. |
| | | */ |
| | | public ECLUpdateMsg(LDAPUpdateMsg updateMsg, MultiDomainServerState cookie, |
| | | String baseDN, int draftChangeNumber) |
| | | String baseDN, int changeNumber) |
| | | { |
| | | this.cookie = cookie; |
| | | this.baseDN = baseDN; |
| | | this.updateMsg = updateMsg; |
| | | this.draftChangeNumber = draftChangeNumber; |
| | | this.changeNumber = changeNumber; |
| | | } |
| | | |
| | | /** |
| | |
| | | this.baseDN = new String(in, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | // Decode the draft changeNumber |
| | | // Decode the changeNumber |
| | | length = getNextLength(in, pos); |
| | | this.draftChangeNumber = Integer.valueOf( |
| | | new String(in, pos, length, "UTF-8")); |
| | | this.changeNumber = Integer.valueOf(new String(in, pos, length, "UTF-8")); |
| | | pos += length + 1; |
| | | |
| | | // Decode the msg |
| | |
| | | length = in.length - pos - 1; |
| | | byte[] encodedMsg = new byte[length]; |
| | | System.arraycopy(in, pos, encodedMsg, 0, length); |
| | | ReplicationMsg rmsg = |
| | | ReplicationMsg.generateMsg( |
| | | ReplicationMsg rmsg = ReplicationMsg.generateMsg( |
| | | encodedMsg, ProtocolVersion.getCurrentVersion()); |
| | | this.updateMsg = (LDAPUpdateMsg)rmsg; |
| | | } |
| | |
| | | return "ECLUpdateMsg:[" + |
| | | " updateMsg: " + updateMsg + |
| | | " cookie: " + cookie + |
| | | " draftChangeNumber: " + draftChangeNumber + |
| | | " changeNumber: " + changeNumber + |
| | | " serviceId: " + baseDN + "]"; |
| | | } |
| | | |
| | |
| | | { |
| | | byte[] byteCookie = String.valueOf(cookie).getBytes("UTF-8"); |
| | | byte[] byteBaseDN = String.valueOf(baseDN).getBytes("UTF-8"); |
| | | byte[] byteDraftChangeNumber = |
| | | Integer.toString(draftChangeNumber).getBytes("UTF-8"); |
| | | byte[] byteChangeNumber = Integer.toString(changeNumber).getBytes("UTF-8"); |
| | | byte[] byteUpdateMsg = updateMsg.getBytes(protocolVersion); |
| | | |
| | | int length = 1 + byteCookie.length + |
| | | 1 + byteBaseDN.length + |
| | | 1 + byteDraftChangeNumber.length + |
| | | 1 + byteChangeNumber.length + |
| | | 1 + byteUpdateMsg.length + 1; |
| | | |
| | | byte[] resultByteArray = new byte[length]; |
| | |
| | | // Encode all fields |
| | | pos = addByteArray(byteCookie, resultByteArray, pos); |
| | | pos = addByteArray(byteBaseDN, resultByteArray, pos); |
| | | pos = addByteArray(byteDraftChangeNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteChangeNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteUpdateMsg, resultByteArray, pos); |
| | | |
| | | return resultByteArray; |
| | | } |
| | | |
| | | /** |
| | | * Setter for the draftChangeNumber of this change. |
| | | * @param draftChangeNumber the provided draftChangeNumber for this change. |
| | | * Setter for the changeNumber of this change. |
| | | * @param changeNumber the provided changeNumber for this change. |
| | | */ |
| | | public void setDraftChangeNumber(int draftChangeNumber) |
| | | public void setChangeNumber(int changeNumber) |
| | | { |
| | | this.draftChangeNumber = draftChangeNumber; |
| | | this.changeNumber = changeNumber; |
| | | } |
| | | |
| | | /** |
| | | * Getter for the draftChangeNumber of this change. |
| | | * @return the draftChangeNumber of this change. |
| | | * Getter for the changeNumber of this change. |
| | | * @return the changeNumber of this change. |
| | | */ |
| | | public int getDraftChangeNumber() |
| | | public int getChangeNumber() |
| | | { |
| | | return this.draftChangeNumber; |
| | | return this.changeNumber; |
| | | } |
| | | |
| | | } |
| | |
| | | * and NOT replication CSNs). |
| | | * TODO: not yet implemented |
| | | */ |
| | | public final static short REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER = 1; |
| | | public final static short REQUEST_TYPE_FROM_CHANGE_NUMBER = 1; |
| | | |
| | | /** |
| | | * This specifies that the ECL is requested only for the entry that have |
| | |
| | | * When eclRequestType = FROM_CHANGE_NUMBER, specifies the provided change |
| | | * number first and last - [CHANGELOG]. |
| | | */ |
| | | private int firstDraftChangeNumber = -1; |
| | | private int lastDraftChangeNumber = -1; |
| | | private int firstChangeNumber = -1; |
| | | private int lastChangeNumber = -1; |
| | | |
| | | /** |
| | | * When eclRequestType = EQUALS_REPL_CHANGE_NUMBER, specifies the provided |
| | |
| | | |
| | | try |
| | | { |
| | | /* first bytes are the header */ |
| | | // first bytes are the header |
| | | int pos = 0; |
| | | |
| | | /* first byte is the type */ |
| | | // first byte is the type |
| | | if (in.length < 1 || in[pos++] != MSG_TYPE_START_ECL_SESSION) |
| | | { |
| | | throw new DataFormatException( |
| | |
| | | |
| | | // sequenceNumber |
| | | length = getNextLength(in, pos); |
| | | firstDraftChangeNumber = |
| | | Integer.valueOf(new String(in, pos, length, "UTF-8")); |
| | | firstChangeNumber = Integer.valueOf(new String(in, pos, length, "UTF-8")); |
| | | pos += length +1; |
| | | |
| | | // stopSequenceNumber |
| | | length = getNextLength(in, pos); |
| | | lastDraftChangeNumber = |
| | | Integer.valueOf(new String(in, pos, length, "UTF-8")); |
| | | lastChangeNumber = Integer.valueOf(new String(in, pos, length, "UTF-8")); |
| | | pos += length +1; |
| | | |
| | | // replication CSN |
| | |
| | | { |
| | | eclRequestType = REQUEST_TYPE_FROM_COOKIE; |
| | | crossDomainServerState = ""; |
| | | firstDraftChangeNumber = -1; |
| | | lastDraftChangeNumber = -1; |
| | | firstChangeNumber = -1; |
| | | lastChangeNumber = -1; |
| | | csn = new CSN(0, 0, 0); |
| | | isPersistent = NON_PERSISTENT; |
| | | operationId = "-1"; |
| | |
| | | try |
| | | { |
| | | byte[] byteMode = toBytes(eclRequestType); |
| | | byte[] byteSequenceNumber = toBytes(firstDraftChangeNumber); |
| | | byte[] byteStopSequenceNumber = toBytes(lastDraftChangeNumber); |
| | | byte[] byteChangeNumber = toBytes(firstChangeNumber); |
| | | byte[] byteStopChangeNumber = toBytes(lastChangeNumber); |
| | | byte[] byteCSN = csn.toString().getBytes("UTF-8"); |
| | | byte[] bytePsearch = toBytes(isPersistent); |
| | | byte[] byteGeneralizedState = toBytes(crossDomainServerState); |
| | |
| | | |
| | | int length = |
| | | byteMode.length + 1 + |
| | | byteSequenceNumber.length + 1 + |
| | | byteStopSequenceNumber.length + 1 + |
| | | byteChangeNumber.length + 1 + |
| | | byteStopChangeNumber.length + 1 + |
| | | byteCSN.length + 1 + |
| | | bytePsearch.length + 1 + |
| | | byteGeneralizedState.length + 1 + |
| | |
| | | int pos = 0; |
| | | resultByteArray[pos++] = MSG_TYPE_START_ECL_SESSION; |
| | | pos = addByteArray(byteMode, resultByteArray, pos); |
| | | pos = addByteArray(byteSequenceNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteStopSequenceNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteChangeNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteStopChangeNumber, resultByteArray, pos); |
| | | pos = addByteArray(byteCSN, resultByteArray, pos); |
| | | pos = addByteArray(bytePsearch, resultByteArray, pos); |
| | | pos = addByteArray(byteGeneralizedState, resultByteArray, pos); |
| | |
| | | return getClass().getCanonicalName() + " [" + |
| | | " requestType="+ eclRequestType + |
| | | " persistentSearch=" + isPersistent + |
| | | " csn=" + csn + |
| | | " firstDraftChangeNumber=" + firstDraftChangeNumber + |
| | | " lastDraftChangeNumber=" + lastDraftChangeNumber + |
| | | " csn=" + csn + |
| | | " firstChangeNumber=" + firstChangeNumber + |
| | | " lastChangeNumber=" + lastChangeNumber + |
| | | " generalizedState=" + crossDomainServerState + |
| | | " operationId=" + operationId + |
| | | " excludedDNs=" + excludedBaseDNs + "]"; |
| | |
| | | * Getter on the changer number start. |
| | | * @return the changer number start. |
| | | */ |
| | | public int getFirstDraftChangeNumber() |
| | | public int getFirstChangeNumber() |
| | | { |
| | | return firstDraftChangeNumber; |
| | | return firstChangeNumber; |
| | | } |
| | | |
| | | /** |
| | | * Getter on the changer number stop. |
| | | * @return the change number stop. |
| | | */ |
| | | public int getLastDraftChangeNumber() |
| | | public int getLastChangeNumber() |
| | | { |
| | | return lastDraftChangeNumber; |
| | | return lastChangeNumber; |
| | | } |
| | | |
| | | /** |
| | | * Setter on the first changer number (as defined by [CHANGELOG]). |
| | | * @param firstDraftChangeNumber the provided first change number. |
| | | * @param firstChangeNumber the provided first change number. |
| | | */ |
| | | public void setFirstDraftChangeNumber(int firstDraftChangeNumber) |
| | | public void setFirstChangeNumber(int firstChangeNumber) |
| | | { |
| | | this.firstDraftChangeNumber = firstDraftChangeNumber; |
| | | this.firstChangeNumber = firstChangeNumber; |
| | | } |
| | | |
| | | /** |
| | | * Setter on the last changer number (as defined by [CHANGELOG]). |
| | | * @param lastDraftChangeNumber the provided last change number. |
| | | * @param lastChangeNumber the provided last change number. |
| | | */ |
| | | public void setLastDraftChangeNumber(int lastDraftChangeNumber) |
| | | public void setLastChangeNumber(int lastChangeNumber) |
| | | { |
| | | this.lastDraftChangeNumber = lastDraftChangeNumber; |
| | | this.lastChangeNumber = lastChangeNumber; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | private boolean draftCompat = false; |
| | | /** |
| | | * Specifies the last draft changer number (seqnum) requested. |
| | | * Specifies the last changer number requested. |
| | | */ |
| | | private int lastDraftCN = 0; |
| | | private int lastChangeNumber = 0; |
| | | /** |
| | | * Specifies whether the draft change number (seqnum) db has been read until |
| | | * its end. |
| | | * Specifies whether the change number db has been read until its end. |
| | | */ |
| | | private boolean isEndOfDraftCNReached = false; |
| | | private boolean isEndOfCNIndexDBReached = false; |
| | | /** |
| | | * Specifies whether the current search has been requested to be persistent |
| | | * or not. |
| | |
| | | "[" + |
| | | "[draftCompat=" + draftCompat + |
| | | "] [persistent=" + isPersistent + |
| | | "] [lastDraftCN=" + lastDraftCN + |
| | | "] [isEndOfDraftCNReached=" + isEndOfDraftCNReached + |
| | | "] [startChangeNumber=" + lastChangeNumber + |
| | | "] [isEndOfDraftCNReached=" + isEndOfCNIndexDBReached + |
| | | "] [searchPhase=" + searchPhase + |
| | | "] [startCookie=" + startCookie + |
| | | "] [previousCookie=" + previousCookie + |
| | |
| | | } |
| | | |
| | | /** |
| | | * Initialize the handler from a provided draft first change number. |
| | | * @param startDraftCN The provided draft first change number. |
| | | * @throws DirectoryException When an error is raised. |
| | | * Initialize the handler from a provided first change number. |
| | | * |
| | | * @param startChangeNumber |
| | | * The provided first change number. |
| | | * @throws DirectoryException |
| | | * When an error is raised. |
| | | */ |
| | | private void initializeCLSearchFromDraftCN(int startDraftCN) |
| | | private void initializeCLSearchFromChangeNumber(int startChangeNumber) |
| | | throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | this.draftCompat = true; |
| | | |
| | | final String providedCookie = findCookie(startDraftCN); |
| | | final String providedCookie = findCookie(startChangeNumber); |
| | | initializeChangelogDomainCtxts(providedCookie, true); |
| | | } |
| | | catch(DirectoryException de) |
| | |
| | | } |
| | | |
| | | /** |
| | | * Finds in the draft changelog DB the cookie corresponding to the passed in |
| | | * startDraftCN. |
| | | * Finds in the {@link ChangeNumberIndexDB} the cookie corresponding to the |
| | | * passed in startChangeNumber. |
| | | * |
| | | * @param startDraftCN |
| | | * the start draftCN coming from the request filter. |
| | | * @return the cookie corresponding to the passed in startDraftCN. |
| | | * @param startChangeNumber |
| | | * the start change number coming from the request filter. |
| | | * @return the cookie corresponding to the passed in startChangeNumber. |
| | | * @throws Exception |
| | | * if a database problem occurred |
| | | * @throws DirectoryException |
| | | * if a database problem occurred |
| | | */ |
| | | private String findCookie(final int startDraftCN) throws ChangelogException, |
| | | private String findCookie(final int startChangeNumber) |
| | | throws ChangelogException, |
| | | DirectoryException |
| | | { |
| | | final ChangeNumberIndexDB cnIndexDB = |
| | | replicationServer.getChangeNumberIndexDB(); |
| | | |
| | | if (startDraftCN <= 1) |
| | | if (startChangeNumber <= 1) |
| | | { |
| | | // Request filter DOES NOT contain any firstDraftCN |
| | | // So we'll generate from the first DraftCN in the DraftCNdb |
| | | // Request filter DOES NOT contain any first change number |
| | | // So we'll generate from the first change number in the DraftCNdb |
| | | if (cnIndexDB.isEmpty()) |
| | | { |
| | | // FIXME JNR if we find a way to make draftCNDb.isEmpty() a non costly |
| | | // operation, then I think we can move this check to the top of this |
| | | // method |
| | | isEndOfDraftCNReached = true; |
| | | isEndOfCNIndexDBReached = true; |
| | | return null; |
| | | } |
| | | |
| | | final int firstDraftCN = cnIndexDB.getFirstDraftCN(); |
| | | final int firstChangeNumber = cnIndexDB.getFirstChangeNumber(); |
| | | final String crossDomainStartState = |
| | | cnIndexDB.getPreviousCookie(firstDraftCN); |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(firstDraftCN); |
| | | cnIndexDB.getPreviousCookie(firstChangeNumber); |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(firstChangeNumber); |
| | | return crossDomainStartState; |
| | | } |
| | | |
| | | // Request filter DOES contain a startDraftCN |
| | | // Request filter DOES contain a startChangeNumber |
| | | |
| | | // Read the draftCNDb to see whether it contains startDraftCN |
| | | String crossDomainStartState = cnIndexDB.getPreviousCookie(startDraftCN); |
| | | // Read the draftCNDb to see whether it contains startChangeNumber |
| | | String crossDomainStartState = |
| | | cnIndexDB.getPreviousCookie(startChangeNumber); |
| | | if (crossDomainStartState != null) |
| | | { |
| | | // found the provided startDraftCN, let's return it |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(startDraftCN); |
| | | // found the provided startChangeNumber, let's return it |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(startChangeNumber); |
| | | return crossDomainStartState; |
| | | } |
| | | |
| | | // startDraftCN provided in the request IS NOT in the DraftCNDb |
| | | // startChangeNumber provided in the request IS NOT in the DraftCNDb |
| | | |
| | | /* |
| | | * Get the draftLimits (from the eligibleCSN got at the beginning of the |
| | | * operation) in order to have the first and possible last DraftCN. |
| | | * operation) in order to have the first and possible last change number. |
| | | */ |
| | | final int[] limits = |
| | | replicationServer.getECLDraftCNLimits(eligibleCSN, excludedBaseDNs); |
| | | final int firstDraftCN = limits[0]; |
| | | final int lastDraftCN = limits[1]; |
| | | final int[] limits = replicationServer.getECLChangeNumberLimits( |
| | | eligibleCSN, excludedBaseDNs); |
| | | final int firstChangeNumber = limits[0]; |
| | | final int lastChangeNumber = limits[1]; |
| | | |
| | | // If the startDraftCN provided is lower than the first Draft CN in |
| | | // If the startChangeNumber provided is lower than the firstChangeNumber in |
| | | // the DB, let's use the lower limit. |
| | | if (startDraftCN < firstDraftCN) |
| | | if (startChangeNumber < firstChangeNumber) |
| | | { |
| | | crossDomainStartState = cnIndexDB.getPreviousCookie(firstDraftCN); |
| | | crossDomainStartState = cnIndexDB.getPreviousCookie(firstChangeNumber); |
| | | if (crossDomainStartState != null) |
| | | { |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(firstDraftCN); |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(firstChangeNumber); |
| | | return crossDomainStartState; |
| | | } |
| | | |
| | | // This should not happen |
| | | isEndOfDraftCNReached = true; |
| | | isEndOfCNIndexDBReached = true; |
| | | return null; |
| | | } |
| | | else if (startDraftCN <= lastDraftCN) |
| | | else if (startChangeNumber <= lastChangeNumber) |
| | | { |
| | | // startDraftCN is between first and potential last and has never |
| | | // startChangeNumber is between first and potential last and has never |
| | | // been returned yet |
| | | if (cnIndexDB.isEmpty()) |
| | | { |
| | | isEndOfDraftCNReached = true; |
| | | isEndOfCNIndexDBReached = true; |
| | | return null; |
| | | } |
| | | |
| | | final int lastKey = cnIndexDB.getLastDraftCN(); |
| | | final int lastKey = cnIndexDB.getLastChangeNumber(); |
| | | crossDomainStartState = cnIndexDB.getPreviousCookie(lastKey); |
| | | cnIndexDBCursor = cnIndexDB.getCursorFrom(lastKey); |
| | | return crossDomainStartState; |
| | |
| | | // this may be very long. Work on perf improvement here. |
| | | } |
| | | |
| | | // startDraftCN is greater than the potential last DraftCN |
| | | // startChangeNumber is greater than the potential lastChangeNumber |
| | | throw new DirectoryException(ResultCode.SUCCESS, Message.raw("")); |
| | | } |
| | | |
| | |
| | | this.operationId = startECLSessionMsg.getOperationId(); |
| | | |
| | | isPersistent = startECLSessionMsg.isPersistent(); |
| | | lastDraftCN = startECLSessionMsg.getLastDraftChangeNumber(); |
| | | lastChangeNumber = startECLSessionMsg.getLastChangeNumber(); |
| | | searchPhase = INIT_PHASE; |
| | | try |
| | | { |
| | |
| | | { |
| | | initializeCLSearchFromGenState(msg.getCrossDomainServerState()); |
| | | } |
| | | else if (requestType == REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER) |
| | | else if (requestType == REQUEST_TYPE_FROM_CHANGE_NUMBER) |
| | | { |
| | | initializeCLSearchFromDraftCN(msg.getFirstDraftChangeNumber()); |
| | | initializeCLSearchFromChangeNumber(msg.getFirstChangeNumber()); |
| | | } |
| | | } |
| | | |
| | |
| | | * ServerHandler. |
| | | * @exception DirectoryException when an error occurs. |
| | | */ |
| | | public ECLUpdateMsg takeECLUpdate() |
| | | throws DirectoryException |
| | | public ECLUpdateMsg takeECLUpdate() throws DirectoryException |
| | | { |
| | | ECLUpdateMsg msg = getNextECLUpdate(); |
| | | |
| | |
| | | (LDAPUpdateMsg) oldestContext.nextMsg, |
| | | null, // cookie will be set later |
| | | oldestContext.rsd.getBaseDn(), |
| | | 0); // draftChangeNumber may be set later |
| | | 0); // changeNumber may be set later |
| | | oldestContext.nextMsg = null; |
| | | |
| | | // Default is not to loop, with one exception |
| | | continueLooping = false; |
| | | if (draftCompat) |
| | | { |
| | | continueLooping = !assignDraftCN(change); |
| | | continueLooping = !assignChangeNumber(change); |
| | | } |
| | | |
| | | // here we have the right oldest change |
| | | // and in the draft case, we have its draft changenumber |
| | | // and in the draft case, we have its change number |
| | | |
| | | // Set and test the domain of the oldestChange see if we reached |
| | | // the end of the phase for this domain |
| | |
| | | { |
| | | oldestContext.active = false; |
| | | } |
| | | if (draftCompat && (lastDraftCN>0) && |
| | | (change.getDraftChangeNumber()>lastDraftCN)) |
| | | if (draftCompat |
| | | && lastChangeNumber > 0 |
| | | && change.getChangeNumber() > lastChangeNumber) |
| | | { |
| | | oldestContext.active = false; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Either retrieves a draftCN from the draftCNDb, or assign a new draftCN and |
| | | * store in the db. |
| | | * Either retrieves a change number from the DB, or assign a new change number |
| | | * and store in the DB. |
| | | * |
| | | * @param oldestChange |
| | | * the oldestChange where to assign the draftCN |
| | | * @return <code>true</code> if a draftCN has been assigned to the provided |
| | | * oldestChange, <code>false</code> otherwise |
| | | * the oldestChange where to assign the change number |
| | | * @return <code>true</code> if a change number has been assigned to the |
| | | * provided oldestChange, <code>false</code> otherwise |
| | | * @throws DirectoryException |
| | | * if any problem occur |
| | | */ |
| | | private boolean assignDraftCN(final ECLUpdateMsg oldestChange) |
| | | private boolean assignChangeNumber(final ECLUpdateMsg oldestChange) |
| | | throws DirectoryException |
| | | { |
| | | // We also need to check if the draftCNdb is consistent with |
| | |
| | | |
| | | while (true) |
| | | { |
| | | if (isEndOfDraftCNReached) |
| | | if (isEndOfCNIndexDBReached) |
| | | { |
| | | // we are at the end of the DraftCNdb in the append mode |
| | | assignNewDraftCNAndStore(oldestChange); |
| | |
| | | } |
| | | |
| | | |
| | | // the next change from the DraftCN db |
| | | // the next change from the CNIndexDB |
| | | CSN csnFromDraftCNDb = cnIndexDBCursor.getCSN(); |
| | | String dnFromDraftCNDb = cnIndexDBCursor.getBaseDN(); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("getNextECLUpdate generating draftCN " |
| | | TRACER.debugInfo("assignChangeNumber() generating change number " |
| | | + " comparing the 2 db DNs :" + dnFromChangelogDb + "?=" |
| | | + csnFromChangelogDb + " timestamps:" |
| | | + new Date(csnFromChangelogDb.getTime()) + " ?older" |
| | |
| | | csnFromDraftCNDb, dnFromDraftCNDb)) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("getNextECLUpdate generating draftCN " |
| | | + " assigning draftCN=" + cnIndexDBCursor.getDraftCN() |
| | | TRACER.debugInfo("assignChangeNumber() generating change number " |
| | | + " assigning changeNumber=" + cnIndexDBCursor.getChangeNumber() |
| | | + " to change=" + oldestChange); |
| | | |
| | | oldestChange.setDraftChangeNumber(cnIndexDBCursor.getDraftCN()); |
| | | oldestChange.setChangeNumber(cnIndexDBCursor.getChangeNumber()); |
| | | return true; |
| | | } |
| | | |
| | |
| | | // it should have been stored lately |
| | | // let's continue to traverse the changelogdb |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("getNextECLUpdate: will skip " + csnFromChangelogDb |
| | | TRACER.debugInfo("assignChangeNumber(): will skip " |
| | | + csnFromChangelogDb |
| | | + " and read next from the regular changelog."); |
| | | return false; // TO BE CHECKED |
| | | } |
| | |
| | | // let's traverse the DraftCNdb searching for the change |
| | | // found in the changelogDb. |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("getNextECLUpdate generating draftCN " |
| | | TRACER.debugInfo("assignChangeNumber() generating change number " |
| | | + " will skip " + csnFromDraftCNDb |
| | | + " and read next change from the DraftCNDb."); |
| | | + " and read next change from the CNIndexDB."); |
| | | |
| | | isEndOfDraftCNReached = !cnIndexDBCursor.next(); |
| | | isEndOfCNIndexDBReached = !cnIndexDBCursor.next(); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("getNextECLUpdate generating draftCN " |
| | | + " has skipped to " + " sn=" + cnIndexDBCursor.getDraftCN() |
| | | + " csn=" + cnIndexDBCursor.getCSN() |
| | | + " End of draftCNDb ?" + isEndOfDraftCNReached); |
| | | TRACER.debugInfo("assignChangeNumber() generating change number has" |
| | | + "skipped to changeNumber=" + cnIndexDBCursor.getChangeNumber() |
| | | + " csn=" + cnIndexDBCursor.getCSN() + " End of CNIndexDB ?" |
| | | + isEndOfCNIndexDBReached); |
| | | } |
| | | catch (ChangelogException e) |
| | | { |
| | |
| | | private void assignNewDraftCNAndStore(ECLUpdateMsg change) |
| | | throws DirectoryException |
| | | { |
| | | // generate a new draftCN and assign to this change |
| | | change.setDraftChangeNumber(replicationServer.getNewDraftCN()); |
| | | // generate a new change number and assign to this change |
| | | change.setChangeNumber(replicationServer.getNewChangeNumber()); |
| | | |
| | | // store in changelogDB the pair |
| | | // (DraftCN of the current change, state before this change) |
| | | // store in CNIndexDB the pair |
| | | // (change number of the current change, state before this change) |
| | | replicationServer.getChangeNumberIndexDB().add( |
| | | change.getDraftChangeNumber(), |
| | | change.getChangeNumber(), |
| | | previousCookie.toString(), |
| | | change.getBaseDN(), |
| | | change.getUpdateMsg().getCSN()); |
| | |
| | | private ChangeNumberIndexDB cnIndexDB; |
| | | |
| | | /** |
| | | * The last value generated of the draft change number. |
| | | * The last value generated of the change number. |
| | | * <p> |
| | | * Guarded by cnIndexDBLock |
| | | **/ |
| | | private int lastGeneratedDraftCN = 0; |
| | | private int lastGeneratedChangeNumber = 0; |
| | | |
| | | /** Used for protecting {@link ChangeNumberIndexDB} related state. */ |
| | | private final Object cnIndexDBLock = new Object(); |
| | |
| | | |
| | | try |
| | | { |
| | | lastGeneratedDraftCN = cnIndexDB.getLastDraftCN(); |
| | | lastGeneratedChangeNumber = cnIndexDB.getLastChangeNumber(); |
| | | } |
| | | catch (Exception ignored) |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | lastGeneratedDraftCN = 0; |
| | | lastGeneratedChangeNumber = 0; |
| | | cnIndexDB = null; |
| | | } |
| | | } |
| | |
| | | if (cnIndexDB == null) |
| | | { |
| | | cnIndexDB = new DraftCNDbHandler(this, this.dbEnv); |
| | | lastGeneratedDraftCN = getLastDraftChangeNumber(); |
| | | lastGeneratedChangeNumber = getLastChangeNumber(); |
| | | } |
| | | return cnIndexDB; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the value of the first draft change number, 0 when db is empty. |
| | | * Get the value of the first change number, 0 when db is empty. |
| | | * |
| | | * @return the first value. |
| | | */ |
| | | public int getFirstDraftChangeNumber() |
| | | public int getFirstChangeNumber() |
| | | { |
| | | synchronized (cnIndexDBLock) |
| | | { |
| | | if (cnIndexDB != null) |
| | | { |
| | | return cnIndexDB.getFirstDraftCN(); |
| | | return cnIndexDB.getFirstChangeNumber(); |
| | | } |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the value of the last draft change number, 0 when db is empty. |
| | | * Get the value of the last change number, 0 when db is empty. |
| | | * |
| | | * @return the last value. |
| | | */ |
| | | public int getLastDraftChangeNumber() |
| | | public int getLastChangeNumber() |
| | | { |
| | | synchronized (cnIndexDBLock) |
| | | { |
| | | if (cnIndexDB != null) |
| | | { |
| | | return cnIndexDB.getLastDraftCN(); |
| | | return cnIndexDB.getLastChangeNumber(); |
| | | } |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Generate a new Draft ChangeNumber. |
| | | * @return The generated Draft ChangeNUmber |
| | | * Generate a new change number. |
| | | * |
| | | * @return The generated change number |
| | | */ |
| | | public int getNewDraftCN() |
| | | public int getNewChangeNumber() |
| | | { |
| | | synchronized (cnIndexDBLock) |
| | | { |
| | | return ++lastGeneratedDraftCN; |
| | | return ++lastGeneratedChangeNumber; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get first and last DraftCN. |
| | | * Get first and last change number. |
| | | * |
| | | * @param crossDomainEligibleCSN The provided crossDomainEligibleCSN used as |
| | | * the upper limit for the lastDraftCN |
| | | * @param excludedBaseDNs The baseDNs that are excluded from the ECL. |
| | | * @return The first and last draftCN. |
| | | * @throws DirectoryException When it happens. |
| | | * @param crossDomainEligibleCSN |
| | | * The provided crossDomainEligibleCSN used as the upper limit for |
| | | * the last change number |
| | | * @param excludedBaseDNs |
| | | * The baseDNs that are excluded from the ECL. |
| | | * @return The first and last change numbers. |
| | | * @throws DirectoryException |
| | | * When it happens. |
| | | */ |
| | | public int[] getECLDraftCNLimits(CSN crossDomainEligibleCSN, |
| | | public int[] getECLChangeNumberLimits(CSN crossDomainEligibleCSN, |
| | | Set<String> excludedBaseDNs) throws DirectoryException |
| | | { |
| | | /* The content of the DraftCNdb depends on the SEARCH operations done before |
| | | * requesting the DraftCN. If no operations, DraftCNdb is empty. |
| | | * requesting the change number. If no operations, DraftCNdb is empty. |
| | | * The limits we want to get are the "potential" limits if a request was |
| | | * done, the DraftCNdb is probably not complete to do that. |
| | | * |
| | | * The first DraftCN is : |
| | | * The first change number is : |
| | | * - the first record from the DraftCNdb |
| | | * - if none because DraftCNdb empty, |
| | | * then |
| | | * if no change in replchangelog then return 0 |
| | | * else return 1 (DraftCN that WILL be returned to next search) |
| | | * else return 1 (change number that WILL be returned to next search) |
| | | * |
| | | * The last DraftCN is : |
| | | * The last change number is : |
| | | * - initialized with the last record from the DraftCNdb (0 if none) |
| | | * and consider the genState associated |
| | | * - to the last DraftCN, we add the count of updates in the replchangelog |
| | | * FROM that genState TO the crossDomainEligibleCSN |
| | | * - to the last change number, we add the count of updates in the |
| | | * replchangelog FROM that genState TO the crossDomainEligibleCSN |
| | | * (this diff is done domain by domain) |
| | | */ |
| | | |
| | | int lastDraftCN; |
| | | int lastChangeNumber; |
| | | boolean dbEmpty = false; |
| | | final ChangeNumberIndexDB cnIndexDB = getChangeNumberIndexDB(); |
| | | |
| | | int firstDraftCN = cnIndexDB.getFirstDraftCN(); |
| | | Map<String,ServerState> domainsServerStateForLastSeqnum = null; |
| | | CSN csnForLastSeqnum = null; |
| | | String domainForLastSeqnum = null; |
| | | if (firstDraftCN < 1) |
| | | int firstChangeNumber = cnIndexDB.getFirstChangeNumber(); |
| | | Map<String, ServerState> domainsServerStateForLastCN = null; |
| | | CSN csnForLastCN = null; |
| | | String domainForLastCN = null; |
| | | if (firstChangeNumber < 1) |
| | | { |
| | | dbEmpty = true; |
| | | firstDraftCN = 0; |
| | | lastDraftCN = 0; |
| | | firstChangeNumber = 0; |
| | | lastChangeNumber = 0; |
| | | } |
| | | else |
| | | { |
| | | lastDraftCN = cnIndexDB.getLastDraftCN(); |
| | | lastChangeNumber = cnIndexDB.getLastChangeNumber(); |
| | | |
| | | // Get the generalized state associated with the current last DraftCN |
| | | // and initializes from it the startStates table |
| | | String lastSeqnumGenState = cnIndexDB.getPreviousCookie(lastDraftCN); |
| | | if ((lastSeqnumGenState != null) && (lastSeqnumGenState.length()>0)) |
| | | // Get the generalized state associated with the current last change |
| | | // number and initializes from it the startStates table |
| | | String lastCNGenState = cnIndexDB.getPreviousCookie(lastChangeNumber); |
| | | if (lastCNGenState != null && lastCNGenState.length() > 0) |
| | | { |
| | | domainsServerStateForLastSeqnum = MultiDomainServerState. |
| | | splitGenStateToServerStates(lastSeqnumGenState); |
| | | domainsServerStateForLastCN = |
| | | MultiDomainServerState.splitGenStateToServerStates(lastCNGenState); |
| | | } |
| | | |
| | | // Get the CSN associated with the current last DraftCN |
| | | csnForLastSeqnum = cnIndexDB.getCSN(lastDraftCN); |
| | | |
| | | // Get the domain associated with the current last DraftCN |
| | | domainForLastSeqnum = cnIndexDB.getBaseDN(lastDraftCN); |
| | | csnForLastCN = cnIndexDB.getCSN(lastChangeNumber); |
| | | domainForLastCN = cnIndexDB.getBaseDN(lastChangeNumber); |
| | | } |
| | | |
| | | long newestDate = 0; |
| | |
| | | continue; |
| | | |
| | | // for this domain, have the state in the replchangelog |
| | | // where the last DraftCN update is |
| | | // where the last change number update is |
| | | long ec; |
| | | if (domainsServerStateForLastSeqnum == null) |
| | | if (domainsServerStateForLastCN == null) |
| | | { |
| | | // Count changes of this domain from the beginning of the changelog |
| | | CSN trimCSN = new CSN(rsd.getLatestDomainTrimDate(), 0, 0); |
| | |
| | | // the date of the most recent change from this last draft record |
| | | if (newestDate == 0) |
| | | { |
| | | newestDate = csnForLastSeqnum.getTime(); |
| | | newestDate = csnForLastCN.getTime(); |
| | | } |
| | | |
| | | // And count changes of this domain from the date of the |
| | | // lastseqnum record (that does not refer to this domain) |
| | | CSN csnx = new CSN(newestDate, csnForLastSeqnum.getSeqnum(), 0); |
| | | CSN csnx = new CSN(newestDate, csnForLastCN.getSeqnum(), 0); |
| | | ec = rsd.getEligibleCount(csnx, crossDomainEligibleCSN); |
| | | |
| | | if (domainForLastSeqnum.equalsIgnoreCase(rsd.getBaseDn())) |
| | | if (domainForLastCN.equalsIgnoreCase(rsd.getBaseDn())) |
| | | ec--; |
| | | } |
| | | |
| | | // cumulates on domains |
| | | lastDraftCN += ec; |
| | | lastChangeNumber += ec; |
| | | |
| | | // DraftCN Db is empty and there are eligible updates in the replication |
| | | // changelog then init first DraftCN |
| | | if ((ec>0) && (firstDraftCN==0)) |
| | | firstDraftCN = 1; |
| | | // CNIndexDB is empty and there are eligible updates in the replication |
| | | // changelog then init first change number |
| | | if (ec > 0 && firstChangeNumber == 0) |
| | | firstChangeNumber = 1; |
| | | } |
| | | |
| | | if (dbEmpty) |
| | | { |
| | | // The database was empty, just keep increasing numbers since last time |
| | | // we generated one DraftCN. |
| | | firstDraftCN += lastGeneratedDraftCN; |
| | | lastDraftCN += lastGeneratedDraftCN; |
| | | // we generated one change number. |
| | | firstChangeNumber += lastGeneratedChangeNumber; |
| | | lastChangeNumber += lastGeneratedChangeNumber; |
| | | } |
| | | return new int[]{firstDraftCN, lastDraftCN}; |
| | | return new int[]{firstChangeNumber, lastChangeNumber}; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | |
| | | /** |
| | | * Get the CSN associated to a provided draft change number. |
| | | * Get the CSN associated to a provided change number. |
| | | * |
| | | * @param draftCN |
| | | * the provided draft change number. |
| | | * @param changeNumber |
| | | * the provided change number. |
| | | * @return the associated CSN, null when none. |
| | | */ |
| | | public CSN getCSN(int draftCN); |
| | | public CSN getCSN(int changeNumber); |
| | | |
| | | /** |
| | | * Get the baseDN associated to a provided draft change number. |
| | | * Get the baseDN associated to a provided change number. |
| | | * |
| | | * @param draftCN |
| | | * the provided draft change number. |
| | | * @param changeNumber |
| | | * the provided change number. |
| | | * @return the baseDN, null when none. |
| | | */ |
| | | public String getBaseDN(int draftCN); |
| | | public String getBaseDN(int changeNumber); |
| | | |
| | | /** |
| | | * Get the previous cookie associated to a provided draft change number. |
| | | * Get the previous cookie associated to a provided change number. |
| | | * |
| | | * @param draftCN |
| | | * the provided draft change number. |
| | | * @param changeNumber |
| | | * the provided change number. |
| | | * @return the previous cookie, null when none. |
| | | */ |
| | | String getPreviousCookie(int draftCN); |
| | | String getPreviousCookie(int changeNumber); |
| | | |
| | | /** |
| | | * Get the firstChange. |
| | | * Get the first change number stored in this DB. |
| | | * |
| | | * @return Returns the first draftCN in the DB. |
| | | * @return Returns the first change number in this DB. |
| | | */ |
| | | int getFirstDraftCN(); |
| | | int getFirstChangeNumber(); |
| | | |
| | | /** |
| | | * Get the lastChange. |
| | | * Get the last change number stored in this DB. |
| | | * |
| | | * @return Returns the last draftCN in the DB |
| | | * @return Returns the last change number in this DB |
| | | */ |
| | | int getLastDraftCN(); |
| | | int getLastChangeNumber(); |
| | | |
| | | /** |
| | | * Add an update to the list of messages that must be saved to the db managed |
| | | * by this db handler. |
| | | * Add an update to the list of messages that must be saved to this DB managed |
| | | * by this DB. |
| | | * <p> |
| | | * This method is blocking if the size of the list of message is larger than |
| | | * its maximum. |
| | | * |
| | | * @param draftCN |
| | | * The draft change number for this record in the db. |
| | | * @param changeNumber |
| | | * The change number for this record in this DB. |
| | | * @param previousCookie |
| | | * The value of the previous cookie. |
| | | * @param baseDN |
| | |
| | | * @param csn |
| | | * The associated replication CSN. |
| | | */ |
| | | void add(int draftCN, String previousCookie, String baseDN, CSN csn); |
| | | void add(int changeNumber, String previousCookie, String baseDN, CSN csn); |
| | | |
| | | /** |
| | | * Generate a new {@link ChangeNumberIndexDBCursor} that allows to browse the |
| | | * db managed by this dbHandler and starting at the position defined by a |
| | | * db managed by this DBHandler and starting at the position defined by a |
| | | * given changeNumber. |
| | | * |
| | | * @param startDraftCN |
| | | * @param startChangeNumber |
| | | * The position where the iterator must start. |
| | | * @return a new ReplicationIterator that allows to browse the db managed by |
| | | * this dbHandler and starting at the position defined by a given |
| | | * @return a new ReplicationIterator that allows to browse this DB managed by |
| | | * this DBHandler and starting at the position defined by a given |
| | | * changeNumber. |
| | | * @throws ChangelogException |
| | | * if a database problem happened. |
| | | */ |
| | | ChangeNumberIndexDBCursor getCursorFrom(int startDraftCN) |
| | | ChangeNumberIndexDBCursor getCursorFrom(int startChangeNumber) |
| | | throws ChangelogException; |
| | | |
| | | /** |
| | |
| | | * Clear the changes from this DB (from both memory cache and DB storage). |
| | | * |
| | | * @throws ChangelogException |
| | | * When an exception occurs while removing the changes from the DB. |
| | | * When an exception occurs while removing the changes from this DB. |
| | | */ |
| | | void clear() throws ChangelogException; |
| | | |
| | |
| | | * the provided baseDN. |
| | | * |
| | | * @param baseDNToClear |
| | | * The baseDN for which we want to remove all records from the |
| | | * DraftCNDb - null means all. |
| | | * The baseDN for which we want to remove all records from this DB, |
| | | * null means all. |
| | | * @throws ChangelogException |
| | | * When an exception occurs while removing the changes from the DB. |
| | | * When an exception occurs while removing the changes from this DB. |
| | | */ |
| | | void clear(String baseDNToClear) throws ChangelogException; |
| | | |
| | | /** |
| | | * Shutdown this dbHandler. |
| | | * Shutdown this DB. |
| | | */ |
| | | void shutdown(); |
| | | |
| | |
| | | String getBaseDN(); |
| | | |
| | | /** |
| | | * Getter for the draftCN field. |
| | | * Getter for the change number field. |
| | | * |
| | | * @return The draft CN field. |
| | | * @return The change number field. |
| | | */ |
| | | int getDraftCN(); |
| | | int getChangeNumber(); |
| | | |
| | | /** |
| | | * Skip to the next record of the database. |
| | |
| | | |
| | | /** |
| | | * Add an entry to the database. |
| | | * @param draftCN the provided draftCN. |
| | | * @param changeNumber the provided change number. |
| | | * |
| | | * @param value the provided value to be stored associated |
| | | * with this draftCN. |
| | | * with this change number. |
| | | * @param domainBaseDN the provided domainBaseDn to be stored associated |
| | | * with this draftCN. |
| | | * with this change number. |
| | | * @param csn the provided replication CSN to be |
| | | * stored associated with this draftCN. |
| | | * stored associated with this change number. |
| | | */ |
| | | public void addEntry(int draftCN, String value, String domainBaseDN, |
| | | public void addEntry(int changeNumber, String value, String domainBaseDN, |
| | | CSN csn) |
| | | { |
| | | try |
| | | { |
| | | DatabaseEntry key = new ReplicationDraftCNKey(draftCN); |
| | | DatabaseEntry key = new ReplicationDraftCNKey(changeNumber); |
| | | DatabaseEntry data = new DraftCNData(value, domainBaseDN, csn); |
| | | |
| | | // Use a transaction so that we can override durability. |
| | |
| | | /** |
| | | * Create a cursor that can be used to search or iterate on this DB. |
| | | * |
| | | * @param draftCN The draftCN from which the cursor must start. |
| | | * @param changeNumber The change number from which the cursor must start. |
| | | * @throws ChangelogException If a database error prevented the cursor |
| | | * creation. |
| | | * @return The ReplServerDBCursor. |
| | | */ |
| | | public DraftCNDBCursor openReadCursor(int draftCN) throws ChangelogException |
| | | public DraftCNDBCursor openReadCursor(int changeNumber) |
| | | throws ChangelogException |
| | | { |
| | | return new DraftCNDBCursor(draftCN); |
| | | return new DraftCNDBCursor(changeNumber); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | /** |
| | | * Read the first Change from the database, 0 when none. |
| | | * @return the first draftCN. |
| | | * @return the first change number. |
| | | */ |
| | | public int readFirstDraftCN() |
| | | public int readFirstChangeNumber() |
| | | { |
| | | try |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Read the last draftCN from the database. |
| | | * @return the last draftCN. |
| | | * Read the last change number from the database. |
| | | * @return the last change number. |
| | | */ |
| | | public int readLastDraftCN() |
| | | public int readLastChangeNumber() |
| | | { |
| | | try |
| | | { |
| | |
| | | private final Transaction txn; |
| | | private final DatabaseEntry key; |
| | | private final DatabaseEntry entry; |
| | | private DraftCNData seqnumData; |
| | | private DraftCNData cnData; |
| | | private boolean isClosed = false; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a cursor that can be used for browsing the db. |
| | | * |
| | | * @param startingDraftCN |
| | | * the draftCN from which the cursor must start. |
| | | * @param startChangeNumber |
| | | * the change number from which the cursor must start. |
| | | * @throws ChangelogException |
| | | * when the startingDraftCN does not exist. |
| | | * when the startChangeNumber does not exist. |
| | | */ |
| | | private DraftCNDBCursor(int startingDraftCN) throws ChangelogException |
| | | private DraftCNDBCursor(int startChangeNumber) throws ChangelogException |
| | | { |
| | | this.key = new ReplicationDraftCNKey(startingDraftCN); |
| | | this.key = new ReplicationDraftCNKey(startChangeNumber); |
| | | this.entry = new DatabaseEntry(); |
| | | |
| | | // Take the lock. From now on, whatever error that happen in the life |
| | |
| | | } |
| | | |
| | | localCursor = db.openCursor(null, null); |
| | | if (startingDraftCN >= 0) |
| | | if (startChangeNumber >= 0) |
| | | { |
| | | if (localCursor.getSearchKey(key, entry, LockMode.DEFAULT) != SUCCESS) |
| | | { |
| | | // We could not move the cursor to the expected startingDraftCN |
| | | // We could not move the cursor to the expected startChangeNumber |
| | | if (localCursor.getSearchKeyRange(key, entry, DEFAULT) != SUCCESS) |
| | | { |
| | | // We could not even move the cursor closed to it => failure |
| | | throw new ChangelogException( |
| | | Message.raw("ChangeLog Draft Change Number " + startingDraftCN |
| | | Message.raw("ChangeLog Change Number " + startChangeNumber |
| | | + " is not available")); |
| | | } |
| | | |
| | |
| | | } |
| | | else |
| | | { |
| | | seqnumData = new DraftCNData(entry.getData()); |
| | | cnData = new DraftCNData(entry.getData()); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | seqnumData = new DraftCNData(entry.getData()); |
| | | cnData = new DraftCNData(entry.getData()); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | try |
| | | { |
| | | if (seqnumData != null) |
| | | if (cnData != null) |
| | | { |
| | | return seqnumData.getValue(); |
| | | return cnData.getValue(); |
| | | } |
| | | } |
| | | catch(Exception e) |
| | |
| | | |
| | | try |
| | | { |
| | | if (seqnumData != null) |
| | | if (cnData != null) |
| | | { |
| | | return seqnumData.getBaseDN(); |
| | | return cnData.getBaseDN(); |
| | | } |
| | | } |
| | | catch(Exception e) |
| | |
| | | |
| | | /** |
| | | * Getter for the integer value of the current cursor, representing |
| | | * the current DraftChangeNumber being processed. |
| | | * the current change number being processed. |
| | | * |
| | | * @return the current DraftCN as an integer. |
| | | * @return the current change number as an integer. |
| | | */ |
| | | public int currentKey() |
| | | { |
| | |
| | | |
| | | try |
| | | { |
| | | if (seqnumData != null) |
| | | if (cnData != null) |
| | | { |
| | | return seqnumData.getCSN(); |
| | | return cnData.getCSN(); |
| | | } |
| | | } |
| | | catch(Exception e) |
| | |
| | | OperationStatus status = cursor.getNext(key, entry, LockMode.DEFAULT); |
| | | if (status != OperationStatus.SUCCESS) |
| | | { |
| | | seqnumData = null; |
| | | cnData = null; |
| | | return false; |
| | | } |
| | | seqnumData = new DraftCNData(entry.getData()); |
| | | cnData = new DraftCNData(entry.getData()); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | |
| | | |
| | | /** |
| | | * Creates a record to be stored in the DraftCNDB. |
| | | * @param value The value (cookie). |
| | | * @param previousCookie The previous cookie. |
| | | * @param baseDN The baseDN (domain DN). |
| | | * @param csn The replication CSN. |
| | | */ |
| | | public DraftCNData(String value, String baseDN, CSN csn) |
| | | public DraftCNData(String previousCookie, String baseDN, CSN csn) |
| | | { |
| | | String record = value + FIELD_SEPARATOR + baseDN + FIELD_SEPARATOR + csn; |
| | | String record = |
| | | previousCookie + FIELD_SEPARATOR + baseDN + FIELD_SEPARATOR + csn; |
| | | setData(getBytes(record)); |
| | | } |
| | | |
| | |
| | | |
| | | /** |
| | | * This class is used for managing the replicationServer database for each |
| | | * server in the topology. |
| | | * It is responsible for efficiently saving the updates that is received from |
| | | * each master server into stable storage. |
| | | * This class is also able to generate a {@link ChangeNumberIndexDBCursor} that |
| | | * can be used to read all changes from a given draft ChangeNumber. |
| | | * server in the topology. It is responsible for efficiently saving the updates |
| | | * that is received from each master server into stable storage. This class is |
| | | * also able to generate a {@link ChangeNumberIndexDBCursor} that can be used to |
| | | * read all changes from a given change number. |
| | | * <p> |
| | | * This class publishes some monitoring information below <code> |
| | | * cn=monitor</code>. |
| | |
| | | |
| | | private DraftCNDB db; |
| | | /** |
| | | * FIXME Is this field that useful? {@link #getFirstDraftCN()} does not even |
| | | * use it! |
| | | * FIXME Is this field that useful? {@link #getFirstChangeNumber()} does not |
| | | * even use it! |
| | | */ |
| | | private int firstDraftCN = NO_KEY; |
| | | private int firstChangeNumber = NO_KEY; |
| | | /** |
| | | * FIXME Is this field that useful? {@link #getLastDraftCN()} does not even |
| | | * use it! It is not even updated. |
| | | * FIXME Is this field that useful? {@link #getLastChangeNumber()} does not |
| | | * even use it! |
| | | */ |
| | | private int lastDraftCN = NO_KEY; |
| | | private int lastChangeNumber = NO_KEY; |
| | | private DbMonitorProvider dbMonitor = new DbMonitorProvider(); |
| | | private boolean shutdown = false; |
| | | private boolean trimDone = false; |
| | |
| | | |
| | | // DB initialization |
| | | db = new DraftCNDB(dbenv); |
| | | firstDraftCN = db.readFirstDraftCN(); |
| | | lastDraftCN = db.readLastDraftCN(); |
| | | firstChangeNumber = db.readFirstChangeNumber(); |
| | | lastChangeNumber = db.readLastChangeNumber(); |
| | | |
| | | // Trimming thread |
| | | thread = new DirectoryThread(this, "Replication DraftCN db"); |
| | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public synchronized void add(int draftCN, String value, String baseDN, |
| | | CSN csn) |
| | | public synchronized void add(int changeNumber, String previousCookie, |
| | | String baseDN, CSN csn) |
| | | { |
| | | db.addEntry(draftCN, value, baseDN, csn); |
| | | db.addEntry(changeNumber, previousCookie, baseDN, csn); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | | "In DraftCNDbhandler.add, added: " |
| | | + " key=" + draftCN |
| | | + " value=" + value |
| | | + " key=" + changeNumber |
| | | + " previousCookie=" + previousCookie |
| | | + " baseDN=" + baseDN |
| | | + " csn=" + csn); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public int getFirstDraftCN() |
| | | public int getFirstChangeNumber() |
| | | { |
| | | return db.readFirstDraftCN(); |
| | | return db.readFirstChangeNumber(); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public int getLastDraftCN() |
| | | public int getLastChangeNumber() |
| | | { |
| | | return db.readLastDraftCN(); |
| | | return db.readLastChangeNumber(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * <ul> |
| | | * <li>open a cursor, check if the next entry exits, then close the cursor |
| | | * </li> |
| | | * <li>call <code>db.readFirstDraftCN() != 0</code></li> |
| | | * <li>call <code>db.readFirstChangeNumber() != 0</code></li> |
| | | * </ul> |
| | | */ |
| | | @Override |
| | |
| | | * <p> |
| | | * This method is only used by unit tests. |
| | | * |
| | | * @param startingDraftCN |
| | | * The draft change number from where to start. |
| | | * @param startChangeNumber |
| | | * The change number from where to start. |
| | | * @return the new cursor. |
| | | * @throws ChangelogException |
| | | * if a database problem occurs. |
| | | */ |
| | | DraftCNDBCursor getReadCursor(int startingDraftCN) throws ChangelogException |
| | | DraftCNDBCursor getReadCursor(int startChangeNumber) |
| | | throws ChangelogException |
| | | { |
| | | return db.openReadCursor(startingDraftCN); |
| | | return db.openReadCursor(startChangeNumber); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ChangeNumberIndexDBCursor getCursorFrom(int startDraftCN) |
| | | public ChangeNumberIndexDBCursor getCursorFrom(int startChangeNumber) |
| | | throws ChangelogException |
| | | { |
| | | return new DraftCNDbIterator(db, startDraftCN); |
| | | return new DraftCNDbIterator(db, startChangeNumber); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | |
| | | final ServerState startState = domain.getStartState(); |
| | | final CSN fcsn = startState.getCSN(csn.getServerId()); |
| | | |
| | | final int currentDraftCN = cursor.currentKey(); |
| | | final int currentChangeNumber = cursor.currentKey(); |
| | | |
| | | if (csn.older(fcsn)) |
| | | { |
| | |
| | | continue; |
| | | } |
| | | |
| | | firstDraftCN = currentDraftCN; |
| | | firstChangeNumber = currentChangeNumber; |
| | | cursor.close(); |
| | | return; |
| | | } |
| | |
| | | { |
| | | List<Attribute> attributes = new ArrayList<Attribute>(); |
| | | attributes.add(Attributes.create("first-draft-changenumber", |
| | | Integer.toString(db.readFirstDraftCN()))); |
| | | Integer.toString(db.readFirstChangeNumber()))); |
| | | attributes.add(Attributes.create("last-draft-changenumber", |
| | | Integer.toString(db.readLastDraftCN()))); |
| | | Integer.toString(db.readLastChangeNumber()))); |
| | | attributes.add(Attributes.create("count", |
| | | Long.toString(count()))); |
| | | return attributes; |
| | |
| | | @Override |
| | | public String getMonitorInstanceName() |
| | | { |
| | | return "Draft Changelog"; |
| | | return "ChangeNumber Index Database"; |
| | | } |
| | | |
| | | /** |
| | |
| | | @Override |
| | | public String toString() |
| | | { |
| | | return "draftCNdb:" + " " + firstDraftCN + " " + lastDraftCN; |
| | | return "draftCNdb:" + " " + firstChangeNumber + " " + lastChangeNumber; |
| | | } |
| | | |
| | | /** |
| | |
| | | public void clear() throws ChangelogException |
| | | { |
| | | db.clear(); |
| | | firstDraftCN = db.readFirstDraftCN(); |
| | | lastDraftCN = db.readLastDraftCN(); |
| | | firstChangeNumber = db.readFirstChangeNumber(); |
| | | lastChangeNumber = db.readLastChangeNumber(); |
| | | } |
| | | |
| | | private ReentrantLock lock = new ReentrantLock(); |
| | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getPreviousCookie(int draftCN) |
| | | public String getPreviousCookie(int changeNumber) |
| | | { |
| | | DraftCNDBCursor draftCNDBCursor = null; |
| | | DraftCNDBCursor cursor = null; |
| | | try |
| | | { |
| | | draftCNDBCursor = db.openReadCursor(draftCN); |
| | | return draftCNDBCursor.currentValue(); |
| | | cursor = db.openReadCursor(changeNumber); |
| | | return cursor.currentValue(); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | debugException("getValue", draftCN, e); |
| | | debugException("getValue", changeNumber, e); |
| | | return null; |
| | | } |
| | | finally |
| | | { |
| | | close(draftCNDBCursor); |
| | | close(cursor); |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public CSN getCSN(int draftCN) |
| | | public CSN getCSN(int changeNumber) |
| | | { |
| | | DraftCNDBCursor draftCNDBCursor = null; |
| | | DraftCNDBCursor cursor = null; |
| | | try |
| | | { |
| | | draftCNDBCursor = db.openReadCursor(draftCN); |
| | | return draftCNDBCursor.currentCSN(); |
| | | cursor = db.openReadCursor(changeNumber); |
| | | return cursor.currentCSN(); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | debugException("getCSN", draftCN, e); |
| | | debugException("getCSN", changeNumber, e); |
| | | return null; |
| | | } |
| | | finally |
| | | { |
| | | close(draftCNDBCursor); |
| | | close(cursor); |
| | | } |
| | | } |
| | | |
| | | /**{@inheritDoc}*/ |
| | | @Override |
| | | public String getBaseDN(int draftCN) |
| | | public String getBaseDN(int changeNumber) |
| | | { |
| | | DraftCNDBCursor draftCNDBCursor = null; |
| | | DraftCNDBCursor cursor = null; |
| | | try |
| | | { |
| | | draftCNDBCursor = db.openReadCursor(draftCN); |
| | | return draftCNDBCursor.currentBaseDN(); |
| | | cursor = db.openReadCursor(changeNumber); |
| | | return cursor.currentBaseDN(); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | debugException("getBaseDN", draftCN, e); |
| | | debugException("getBaseDN", changeNumber, e); |
| | | return null; |
| | | } |
| | | finally |
| | | { |
| | | close(draftCNDBCursor); |
| | | close(cursor); |
| | | } |
| | | } |
| | | |
| | | private void debugException(String methodName, int draftCN, Exception e) |
| | | private void debugException(String methodName, int changeNumber, Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("In DraftCNDbHandler." + methodName + "(), read: " |
| | | + " key=" + draftCN + " value returned is null" |
| | | + " first="+ db.readFirstDraftCN() |
| | | + " last=" + db.readLastDraftCN() |
| | | + " key=" + changeNumber + " value returned is null" |
| | | + " first="+ db.readFirstChangeNumber() |
| | | + " last=" + db.readLastChangeNumber() |
| | | + " count=" + db.count() |
| | | + " exception " + e + " " + e.getMessage()); |
| | | } |
| | |
| | | private DraftCNDBCursor draftCNDbCursor; |
| | | |
| | | /** |
| | | * Creates a new ReplicationIterator. |
| | | * All created iterator must be released by the caller using the |
| | | * releaseCursor() method. |
| | | * Creates a new ReplicationIterator. All created iterator must be released by |
| | | * the caller using the {@link #close()} method. |
| | | * |
| | | * @param db The db where the iterator must be created. |
| | | * @param startDraftCN The draft CN after which the iterator |
| | | * must start. |
| | | * @throws ChangelogException If a database problem happened. |
| | | * @param db |
| | | * The db where the iterator must be created. |
| | | * @param startChangeNumber |
| | | * The change number after which the iterator must start. |
| | | * @throws ChangelogException |
| | | * If a database problem happened. |
| | | */ |
| | | public DraftCNDbIterator(DraftCNDB db, int startDraftCN) |
| | | public DraftCNDbIterator(DraftCNDB db, int startChangeNumber) |
| | | throws ChangelogException |
| | | { |
| | | draftCNDbCursor = db.openReadCursor(startDraftCN); |
| | | draftCNDbCursor = db.openReadCursor(startChangeNumber); |
| | | if (draftCNDbCursor == null) |
| | | { |
| | | throw new ChangelogException(Message.raw("no new change")); |
| | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public int getDraftCN() |
| | | public int getChangeNumber() |
| | | { |
| | | ReplicationDraftCNKey sk = (ReplicationDraftCNKey) draftCNDbCursor.getKey(); |
| | | int currentSeqnum = sk.getDraftCN(); |
| | | return currentSeqnum; |
| | | return ((ReplicationDraftCNKey) draftCNDbCursor.getKey()).getChangeNumber(); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | |
| | | { |
| | | try |
| | | { |
| | | // Opens the database for seqnum associated to this domain. |
| | | // Opens the database for change number associated to this domain. |
| | | // Create it if it does not already exist. |
| | | return openDatabase("draftcndb"); |
| | | } |
| | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * Creates a new ReplicationKey from the given draft ChangeNumber. |
| | | * @param draftCN The draft change number to use. |
| | | * Creates a new ReplicationKey from the given change number. |
| | | * @param changeNumber The change number to use. |
| | | */ |
| | | public ReplicationDraftCNKey(int draftCN) |
| | | public ReplicationDraftCNKey(int changeNumber) |
| | | { |
| | | try |
| | | { |
| | | String s = String.valueOf(draftCN); |
| | | String s = String.valueOf(changeNumber); |
| | | int a = 16-s.length(); |
| | | String sscn = "0000000000000000".substring(0, a) + s; |
| | | // Should it use StaticUtils.getBytes() to increase performances? |
| | | this.setData(sscn.getBytes("UTF-8")); |
| | | setData(sscn.getBytes("UTF-8")); |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | // Should never happens, UTF-8 is always supported |
| | |
| | | } |
| | | |
| | | /** |
| | | * Getter for the draft change number associated with this key. |
| | | * @return the draft change number associated with this key. |
| | | * Getter for the change number associated with this key. |
| | | * @return the change number associated with this key. |
| | | */ |
| | | public int getDraftCN() |
| | | public int getChangeNumber() |
| | | { |
| | | String s = new String(this.getData()); |
| | | return Integer.valueOf(s); |
| | | return Integer.valueOf(new String(getData())); |
| | | } |
| | | } |
| | |
| | | clientConnection = getClientConnection(); |
| | | startECLSessionMsg = new StartECLSessionMsg(); |
| | | |
| | | // Set default behavior as "from draft change number". |
| | | // Set default behavior as "from change number". |
| | | // "from cookie" is set only when cookie is provided. |
| | | startECLSessionMsg.setECLRequestType( |
| | | StartECLSessionMsg.REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER); |
| | | StartECLSessionMsg.REQUEST_TYPE_FROM_CHANGE_NUMBER); |
| | | |
| | | // Set a string operationid that will help correlate any error message |
| | | // logged for this operation with the 'real' client operation. |
| | |
| | | // format) |
| | | addMsg.getEntryUUID(), |
| | | eclAttributes, // entry attributes |
| | | eclmsg.getDraftChangeNumber(), "add", changeInitiatorsName); |
| | | eclmsg.getChangeNumber(), "add", changeInitiatorsName); |
| | | } |
| | | else if (msg instanceof ModifyCommonMsg) |
| | | { |
| | |
| | | modifyMsg.getCSN(), ldifChanges, |
| | | modifyMsg.getEntryUUID(), |
| | | modifyMsg.getEclIncludes(), // entry attributes |
| | | eclmsg.getDraftChangeNumber(), changeType, |
| | | eclmsg.getChangeNumber(), changeType, |
| | | changeInitiatorsName); |
| | | |
| | | if (modifyMsg instanceof ModifyDNMsg) |
| | |
| | | null, // no changes |
| | | delMsg.getEntryUUID(), |
| | | delMsg.getEclIncludes(), // entry attributes |
| | | eclmsg.getDraftChangeNumber(), "delete", |
| | | eclmsg.getChangeNumber(), "delete", |
| | | delMsg.getInitiatorsName()); |
| | | } |
| | | |
| | |
| | | * @param clearLDIFchanges The provided LDIF changes for ADD and MODIFY |
| | | * @param targetUUID The provided targetUUID. |
| | | * @param includedAttributes The provided attributes to include |
| | | * @param draftChangenumber The provided draft change number (integer) |
| | | * @param changenumber The provided change number (integer) |
| | | * @param changetype The provided change type (add, ...) |
| | | * @param changeInitiatorsName The provided initiators name |
| | | * @return The created ECL entry. |
| | |
| | | String clearLDIFchanges, |
| | | String targetUUID, |
| | | List<RawAttribute> includedAttributes, |
| | | int draftChangenumber, |
| | | int changenumber, |
| | | String changetype, |
| | | String changeInitiatorsName) |
| | | throws DirectoryException |
| | | { |
| | | String dnString; |
| | | if (draftChangenumber == 0) |
| | | if (changenumber == 0) |
| | | { |
| | | // Draft uncompat mode |
| | | dnString = "replicationCSN=" + csn + "," + baseDN + "," |
| | |
| | | else |
| | | { |
| | | // Draft compat mode |
| | | dnString = "changeNumber=" + draftChangenumber + "," |
| | | dnString = "changeNumber=" + changenumber + "," |
| | | + ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT; |
| | | } |
| | | |
| | |
| | | |
| | | // REQUIRED attributes |
| | | |
| | | // ECL Changelog draft change number |
| | | // ECL Changelog change number |
| | | addAttributeByType("changenumber", "changeNumber", String |
| | | .valueOf(draftChangenumber), uAttrs, operationalAttrs); |
| | | .valueOf(changenumber), uAttrs, operationalAttrs); |
| | | |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME); |
| | | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // ?? |
| | |
| | | } |
| | | |
| | | StartECLSessionMsg msg = evaluateSearchParameters2(sf); |
| | | startCLmsg.setFirstDraftChangeNumber(msg.getFirstDraftChangeNumber()); |
| | | startCLmsg.setLastDraftChangeNumber(msg.getLastDraftChangeNumber()); |
| | | startCLmsg.setFirstChangeNumber(msg.getFirstChangeNumber()); |
| | | startCLmsg.setLastChangeNumber(msg.getLastChangeNumber()); |
| | | startCLmsg.setCSN(msg.getCSN()); |
| | | } |
| | | |
| | |
| | | throws DirectoryException |
| | | { |
| | | StartECLSessionMsg startCLmsg = new StartECLSessionMsg(); |
| | | startCLmsg.setFirstDraftChangeNumber(-1); |
| | | startCLmsg.setLastDraftChangeNumber(-1); |
| | | startCLmsg.setFirstChangeNumber(-1); |
| | | startCLmsg.setLastChangeNumber(-1); |
| | | startCLmsg.setCSN(new CSN(0, 0, 0)); |
| | | |
| | | // If there's no filter, just return |
| | |
| | | { |
| | | int sn = Integer.decode( |
| | | sf.getAssertionValue().getNormalizedValue().toString()); |
| | | startCLmsg.setFirstDraftChangeNumber(sn); |
| | | startCLmsg.setFirstChangeNumber(sn); |
| | | return startCLmsg; |
| | | } |
| | | else if (matches(sf, FilterType.LESS_OR_EQUAL, "changeNumber")) |
| | | { |
| | | int sn = Integer.decode( |
| | | sf.getAssertionValue().getNormalizedValue().toString()); |
| | | startCLmsg.setLastDraftChangeNumber(sn); |
| | | startCLmsg.setLastChangeNumber(sn); |
| | | return startCLmsg; |
| | | } |
| | | else if (matches(sf, FilterType.EQUALITY, "replicationcsn")) |
| | |
| | | { |
| | | int sn = Integer.decode( |
| | | sf.getAssertionValue().getNormalizedValue().toString()); |
| | | startCLmsg.setFirstDraftChangeNumber(sn); |
| | | startCLmsg.setLastDraftChangeNumber(sn); |
| | | startCLmsg.setFirstChangeNumber(sn); |
| | | startCLmsg.setLastChangeNumber(sn); |
| | | return startCLmsg; |
| | | } |
| | | else if (sf.getFilterType() == FilterType.AND) |
| | |
| | | if (sfs.length > 0) |
| | | { |
| | | m1 = evaluateSearchParameters2(sfs[0]); |
| | | l1 = m1.getLastDraftChangeNumber(); |
| | | f1 = m1.getFirstDraftChangeNumber(); |
| | | l1 = m1.getLastChangeNumber(); |
| | | f1 = m1.getFirstChangeNumber(); |
| | | } |
| | | if (sfs.length > 1) |
| | | { |
| | | m2 = evaluateSearchParameters2(sfs[1]); |
| | | l2 = m2.getLastDraftChangeNumber(); |
| | | f2 = m2.getFirstDraftChangeNumber(); |
| | | l2 = m2.getLastChangeNumber(); |
| | | f2 = m2.getFirstChangeNumber(); |
| | | } |
| | | if (l1 == -1) |
| | | startCLmsg.setLastDraftChangeNumber(l2); |
| | | startCLmsg.setLastChangeNumber(l2); |
| | | else if (l2 == -1) |
| | | startCLmsg.setLastDraftChangeNumber(l1); |
| | | startCLmsg.setLastChangeNumber(l1); |
| | | else |
| | | startCLmsg.setLastDraftChangeNumber(Math.min(l1, l2)); |
| | | startCLmsg.setLastChangeNumber(Math.min(l1, l2)); |
| | | |
| | | startCLmsg.setFirstDraftChangeNumber(Math.max(f1,f2)); |
| | | startCLmsg.setFirstChangeNumber(Math.max(f1,f2)); |
| | | return startCLmsg; |
| | | } |
| | | else |
| | |
| | | |
| | | ECLCompatNoControl(1); |
| | | |
| | | // Write additional changes and read ECL from a provided draft change number |
| | | // Write additional changes and read ECL from a provided change number |
| | | ECLCompatWriteReadAllOps(5); |
| | | replicationServer.clearDb(); |
| | | } |
| | |
| | | @Test(enabled=true, groups="slow", dependsOnMethods = { "ECLReplicationServerTest"}) |
| | | public void ECLReplicationServerFullTest14() throws Exception |
| | | { |
| | | // Request from an invalid draft change number |
| | | // Request from an invalid change number |
| | | ECLCompatBadSeqnum(); |
| | | } |
| | | |
| | |
| | | // Write 4 changes and read ECL from start |
| | | ECLCompatWriteReadAllOps(1); |
| | | |
| | | // Write 4 additional changes and read ECL from a provided draft change number |
| | | // Write 4 additional changes and read ECL from a provided change number |
| | | int ts = ECLCompatWriteReadAllOps(5); |
| | | |
| | | // Test request from a provided change number - read 6 |
| | |
| | | // Test request from a provided change number interval - read 5-7 |
| | | ECLCompatReadFromTo(5,7); |
| | | |
| | | // Test first and last draft changenumber |
| | | // Test first and last change number |
| | | ECLCompatTestLimits(1,8, true); |
| | | |
| | | // Test first and last draft changenumber, a dd a new change, do not |
| | | // Test first and last change number, a dd a new change, do not |
| | | // search again the ECL, but search for first and last |
| | | ECLCompatTestLimitsAndAdd(1,8, ts); |
| | | |
| | |
| | | debugInfo(tn, "Ending test successfully"); |
| | | } |
| | | |
| | | private int ECLCompatWriteReadAllOps(int firstDraftChangeNumber) |
| | | private int ECLCompatWriteReadAllOps(int firstChangeNumber) |
| | | throws Exception |
| | | { |
| | | String tn = "ECLCompatWriteReadAllOps/" + firstDraftChangeNumber; |
| | | String tn = "ECLCompatWriteReadAllOps/" + firstChangeNumber; |
| | | debugInfo(tn, "Starting test\n\n"); |
| | | final int nbChanges = 4; |
| | | |
| | |
| | | InternalSearchOperation searchOp = searchOnChangelog(filter, tn, SUCCESS); |
| | | |
| | | // test 4 entries returned |
| | | assertEntries(searchOp.getSearchEntries(), firstDraftChangeNumber, tn, |
| | | assertEntries(searchOp.getSearchEntries(), firstChangeNumber, tn, |
| | | ldifWriter, user1entryUUID, csns[0], gblCSN, csns[2], csns[3]); |
| | | |
| | | stop(server01); |
| | | |
| | | // Test with filter on draft changenumber |
| | | filter = "(&(targetdn=*"+tn.toLowerCase()+"*,o=test)(&(changenumber>="+ |
| | | firstDraftChangeNumber+")(changenumber<="+(firstDraftChangeNumber+3)+")))"; |
| | | // Test with filter on change number |
| | | filter = |
| | | "(&(targetdn=*" + tn.toLowerCase() + "*,o=test)" + |
| | | "(&(changenumber>=" + firstChangeNumber + ")" + |
| | | "(changenumber<=" + (firstChangeNumber + 3) + ")))"; |
| | | searchOp = searchOnChangelog(filter, tn, SUCCESS); |
| | | |
| | | assertEntries(searchOp.getSearchEntries(), firstDraftChangeNumber, tn, |
| | | assertEntries(searchOp.getSearchEntries(), firstChangeNumber, tn, |
| | | ldifWriter, user1entryUUID, csns[0], gblCSN, csns[2], csns[3]); |
| | | assertEquals(searchOp.getSearchEntries().size(), nbChanges); |
| | | } |
| | |
| | | } |
| | | |
| | | private void assertEntries(List<SearchResultEntry> entries, |
| | | int firstDraftChangeNumber, String tn, LDIFWriter ldifWriter, |
| | | int firstChangeNumber, String tn, LDIFWriter ldifWriter, |
| | | String user1entryUUID, CSN... csns) throws Exception |
| | | { |
| | | debugAndWriteEntries(ldifWriter, entries, tn); |
| | |
| | | { |
| | | i++; |
| | | |
| | | assertDnEquals(resultEntry, firstDraftChangeNumber, i - 1); |
| | | checkValue(resultEntry, "changenumber", String.valueOf(firstDraftChangeNumber + i - 1)); |
| | | assertDnEquals(resultEntry, firstChangeNumber, i - 1); |
| | | checkValue(resultEntry, "changenumber", String.valueOf(firstChangeNumber + i - 1)); |
| | | checkValue(resultEntry, "targetentryuuid", user1entryUUID); |
| | | checkValue(resultEntry, "replicaidentifier", String.valueOf(SERVER_ID_1)); |
| | | final CSN csn = csns[i - 1]; |
| | |
| | | } |
| | | } |
| | | |
| | | private void assertDnEquals(SearchResultEntry resultEntry, int draftCN, int i) |
| | | private void assertDnEquals(SearchResultEntry resultEntry, int changeNumber, int i) |
| | | { |
| | | String actualDN = resultEntry.getDN().toNormalizedString(); |
| | | String expectedDN = "changenumber=" + (draftCN + i) + ",cn=changelog"; |
| | | String expectedDN = "changenumber=" + (changeNumber + i) + ",cn=changelog"; |
| | | assertThat(actualDN).isEqualToIgnoringCase(expectedDN); |
| | | } |
| | | |
| | | private void ECLCompatReadFrom(int firstDraftChangeNumber) throws Exception |
| | | private void ECLCompatReadFrom(int firstChangeNumber) throws Exception |
| | | { |
| | | String tn = "ECLCompatReadFrom/" + firstDraftChangeNumber; |
| | | String tn = "ECLCompatReadFrom/" + firstChangeNumber; |
| | | debugInfo(tn, "Starting test\n\n"); |
| | | |
| | | LDIFWriter ldifWriter = getLDIFWriter(); |
| | |
| | | |
| | | String user1entryUUID = "11111111-1112-1113-1114-111111111115"; |
| | | |
| | | String filter = "(changenumber=" + firstDraftChangeNumber + ")"; |
| | | String filter = "(changenumber=" + firstChangeNumber + ")"; |
| | | InternalSearchOperation searchOp = searchOnChangelog(filter, tn, SUCCESS); |
| | | |
| | | List<SearchResultEntry> entries = searchOp.getSearchEntries(); |
| | |
| | | * Process similar search as but only check that there's no control returned |
| | | * as part of the entry. |
| | | */ |
| | | private void ECLCompatNoControl(int firstDraftChangeNumber) throws Exception |
| | | private void ECLCompatNoControl(int firstChangeNumber) throws Exception |
| | | { |
| | | String tn = "ECLCompatNoControl/" + firstDraftChangeNumber; |
| | | String tn = "ECLCompatNoControl/" + firstChangeNumber; |
| | | debugInfo(tn, "Starting test\n\n"); |
| | | |
| | | // Creates broker on o=test |
| | |
| | | openReplicationSession(DN.decode(TEST_ROOT_DN_STRING), SERVER_ID_1, 100, |
| | | replicationServerPort, brokerSessionTimeout, true); |
| | | |
| | | String filter = "(changenumber=" + firstDraftChangeNumber + ")"; |
| | | String filter = "(changenumber=" + firstChangeNumber + ")"; |
| | | InternalSearchOperation searchOp = searchOnChangelog(filter, tn, SUCCESS); |
| | | |
| | | List<SearchResultEntry> entries = searchOp.getSearchEntries(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Read the ECL in compat mode from firstDraftChangeNumber and to |
| | | * lastDraftChangeNumber. |
| | | * Read the ECL in compat mode from firstChangeNumber and to lastChangeNumber. |
| | | * |
| | | * @param firstDraftChangeNumber the lower limit |
| | | * @param lastDraftChangeNumber the higher limit |
| | | * @param firstChangeNumber |
| | | * the lower limit |
| | | * @param lastChangeNumber |
| | | * the higher limit |
| | | */ |
| | | private void ECLCompatReadFromTo(int firstDraftChangeNumber, |
| | | int lastDraftChangeNumber) throws Exception |
| | | private void ECLCompatReadFromTo(int firstChangeNumber, int lastChangeNumber) throws Exception |
| | | { |
| | | String tn = "ECLCompatReadFromTo/" + firstDraftChangeNumber + "/" + lastDraftChangeNumber; |
| | | String tn = "ECLCompatReadFromTo/" + firstChangeNumber + "/" + lastChangeNumber; |
| | | debugInfo(tn, "Starting test\n\n"); |
| | | |
| | | String filter = "(&(changenumber>=" + firstDraftChangeNumber + ")" + |
| | | "(changenumber<="+ lastDraftChangeNumber + "))"; |
| | | String filter = |
| | | "(&(changenumber>=" + firstChangeNumber + ")" + "(changenumber<=" + lastChangeNumber + "))"; |
| | | InternalSearchOperation searchOp = searchOnChangelog(filter, tn, SUCCESS); |
| | | assertEquals(searchOp.getSearchEntries().size(), |
| | | lastDraftChangeNumber - firstDraftChangeNumber + 1); |
| | | assertEquals(searchOp.getSearchEntries().size(), lastChangeNumber - firstChangeNumber + 1); |
| | | debugAndWriteEntries(null, searchOp.getSearchEntries(), tn); |
| | | |
| | | debugInfo(tn, "Ending test with success"); |
| | | } |
| | | |
| | | /** |
| | | * Read the ECL in compat mode providing an unknown draft changenumber. |
| | | * Read the ECL in compat mode providing an unknown change number. |
| | | */ |
| | | private void ECLCompatBadSeqnum() throws Exception |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Read the ECL in compat mode providing an unknown draft changenumber. |
| | | * Read the ECL in compat mode providing an unknown change number. |
| | | */ |
| | | private void ECLFilterOnReplicationCsn() throws Exception |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Test that different values of filter are correctly decoded |
| | | * to find if the search op on the ECL can be optimized |
| | | * regarding the Draft changenumbers. |
| | | * Test that different values of filter are correctly decoded to find if the |
| | | * search op on the ECL can be optimized regarding the change numbers. |
| | | */ |
| | | private void ECLFilterTest() throws Exception |
| | | { |
| | |
| | | final StartECLSessionMsg startCLmsg = new StartECLSessionMsg(); |
| | | ECLSearchOperation.evaluateSearchParameters(startCLmsg, |
| | | baseDN, SearchFilter.createFilterFromString("(&(changenumber>=2)(changenumber<+5))")); |
| | | assertEquals(startCLmsg.getFirstDraftChangeNumber(), 1); |
| | | assertEquals(startCLmsg.getFirstChangeNumber(), 1); |
| | | } |
| | | catch (DirectoryException expected) |
| | | { |
| | |
| | | } |
| | | |
| | | private StartECLSessionMsg evaluateSearchParameters(DN baseDN, |
| | | int firstDraftCN, int lastDraftCN, String filterString) throws Exception |
| | | int firstChangeNumber, int lastChangeNumber, String filterString) throws Exception |
| | | { |
| | | final StartECLSessionMsg startCLmsg = new StartECLSessionMsg(); |
| | | ECLSearchOperation.evaluateSearchParameters(startCLmsg, baseDN, |
| | | SearchFilter.createFilterFromString(filterString)); |
| | | assertEquals(startCLmsg.getFirstDraftChangeNumber(), firstDraftCN); |
| | | assertEquals(startCLmsg.getLastDraftChangeNumber(), lastDraftCN); |
| | | assertEquals(startCLmsg.getFirstChangeNumber(), firstChangeNumber); |
| | | assertEquals(startCLmsg.getLastChangeNumber(), lastChangeNumber); |
| | | return startCLmsg; |
| | | } |
| | | |
| | |
| | | ReplicationServer rs = eclwe.getReplicationServer(); |
| | | rs.disableEligibility(excludedDomains); |
| | | long t1 = TimeThread.getTime(); |
| | | int[] limitss = replicationServer.getECLDraftCNLimits( |
| | | int[] limits = replicationServer.getECLChangeNumberLimits( |
| | | replicationServer.getEligibleCSN(), excludedDomains); |
| | | assertEquals(limitss[1], maxMsg); |
| | | assertEquals(limits[1], maxMsg); |
| | | long t2 = TimeThread.getTime(); |
| | | debugInfo(tn, "Perfs - " + maxMsg + " counted in (ms):" + (t2 - t1)); |
| | | |
| | |
| | | CSN csn = new CSN(TimeThread.getTime(), 123, 45); |
| | | op.setAttachment(SYNCHROCONTEXT, new DeleteContext(csn, "uniqueid")); |
| | | DeleteMsg delmsg = new DeleteMsg(op); |
| | | int draftCN = 21; |
| | | int changeNumber = 21; |
| | | |
| | | String baseDN = "dc=example,dc=com"; |
| | | |
| | |
| | | "o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;"); |
| | | |
| | | // Constructor test |
| | | ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, baseDN, draftCN); |
| | | ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, baseDN, changeNumber); |
| | | assertTrue(msg1.getCookie().equalsTo(cookie)); |
| | | assertTrue(msg1.getBaseDN().equalsIgnoreCase(baseDN)); |
| | | assertEquals(msg1.getDraftChangeNumber(), draftCN); |
| | | assertEquals(msg1.getChangeNumber(), changeNumber); |
| | | DeleteMsg delmsg2 = (DeleteMsg)msg1.getUpdateMsg(); |
| | | assertEquals(delmsg.compareTo(delmsg2), 0); |
| | | |
| | |
| | | assertTrue(msg2.getCookie().equalsTo(cookie)); |
| | | assertTrue(msg2.getBaseDN().equalsIgnoreCase(msg1.getBaseDN())); |
| | | assertTrue(msg2.getBaseDN().equalsIgnoreCase(baseDN)); |
| | | assertEquals(msg2.getDraftChangeNumber(), msg1.getDraftChangeNumber()); |
| | | assertEquals(msg2.getDraftChangeNumber(), draftCN); |
| | | assertEquals(msg2.getChangeNumber(), msg1.getChangeNumber()); |
| | | assertEquals(msg2.getChangeNumber(), changeNumber); |
| | | |
| | | DeleteMsg delmsg1 = (DeleteMsg)msg1.getUpdateMsg(); |
| | | delmsg2 = (DeleteMsg)msg2.getUpdateMsg(); |
| | |
| | | msg.setCSN(csn); |
| | | msg.setCrossDomainServerState("fakegenstate"); |
| | | msg.setPersistent(StartECLSessionMsg.PERSISTENT); |
| | | msg.setFirstDraftChangeNumber(13); |
| | | msg.setLastDraftChangeNumber(14); |
| | | msg.setFirstChangeNumber(13); |
| | | msg.setLastChangeNumber(14); |
| | | msg.setECLRequestType((short) 3); |
| | | msg.setOperationId("fakeopid"); |
| | | String dn1 = "cn=admin data"; |
| | |
| | | // test equality between the two copies |
| | | assertEquals(msg.getCSN(), newMsg.getCSN()); |
| | | assertEquals(msg.isPersistent(), newMsg.isPersistent()); |
| | | assertEquals(msg.getFirstDraftChangeNumber(), newMsg.getFirstDraftChangeNumber()); |
| | | assertEquals(msg.getFirstChangeNumber(), newMsg.getFirstChangeNumber()); |
| | | assertEquals(msg.getECLRequestType(), newMsg.getECLRequestType()); |
| | | assertEquals(msg.getLastDraftChangeNumber(), newMsg.getLastDraftChangeNumber()); |
| | | assertEquals(msg.getLastChangeNumber(), newMsg.getLastChangeNumber()); |
| | | assertTrue(msg.getCrossDomainServerState().equalsIgnoreCase(newMsg.getCrossDomainServerState())); |
| | | assertTrue(msg.getOperationId().equalsIgnoreCase(newMsg.getOperationId())); |
| | | Set<String> dns2 = newMsg.getExcludedBaseDNs(); |
| | |
| | | handler.setPurgeDelay(0); |
| | | |
| | | // Prepare data to be stored in the db |
| | | int sn1 = 3; |
| | | int sn2 = 4; |
| | | int sn3 = 5; |
| | | int cn1 = 3; |
| | | int cn2 = 4; |
| | | int cn3 = 5; |
| | | |
| | | String value1 = "value1"; |
| | | String value2 = "value2"; |
| | |
| | | CSN csn3 = gen.newCSN(); |
| | | |
| | | // Add records |
| | | handler.add(sn1, value1, baseDN1, csn1); |
| | | handler.add(sn2, value2, baseDN2, csn2); |
| | | handler.add(sn3, value3, baseDN3, csn3); |
| | | handler.add(cn1, value1, baseDN1, csn1); |
| | | handler.add(cn2, value2, baseDN2, csn2); |
| | | handler.add(cn3, value3, baseDN3, csn3); |
| | | |
| | | // The ChangeNumber should not get purged |
| | | final int firstDraftCN = handler.getFirstDraftCN(); |
| | | assertEquals(firstDraftCN, sn1); |
| | | assertEquals(handler.getLastDraftCN(), sn3); |
| | | final int firstChangeNumber = handler.getFirstChangeNumber(); |
| | | assertEquals(firstChangeNumber, cn1); |
| | | assertEquals(handler.getLastChangeNumber(), cn3); |
| | | |
| | | DraftCNDBCursor dbc = handler.getReadCursor(firstDraftCN); |
| | | DraftCNDBCursor dbc = handler.getReadCursor(firstChangeNumber); |
| | | try |
| | | { |
| | | assertEquals(dbc.currentCSN(), csn1); |
| | |
| | | { |
| | | Thread.sleep(200); |
| | | } |
| | | assertEquals(handler.getFirstDraftCN(), 0); |
| | | assertEquals(handler.getLastDraftCN(), 0); |
| | | |
| | | |
| | | } finally |
| | | assertEquals(handler.getFirstChangeNumber(), 0); |
| | | assertEquals(handler.getLastChangeNumber(), 0); |
| | | } |
| | | finally |
| | | { |
| | | if (handler != null) |
| | | handler.shutdown(); |
| | |
| | | assertTrue(handler.isEmpty()); |
| | | |
| | | // Prepare data to be stored in the db |
| | | int sn1 = 3; |
| | | int sn2 = 4; |
| | | int sn3 = 5; |
| | | int cn1 = 3; |
| | | int cn2 = 4; |
| | | int cn3 = 5; |
| | | |
| | | String value1 = "value1"; |
| | | String value2 = "value2"; |
| | |
| | | CSN csn3 = gen.newCSN(); |
| | | |
| | | // Add records |
| | | handler.add(sn1, value1, baseDN1, csn1); |
| | | handler.add(sn2, value2, baseDN2, csn2); |
| | | handler.add(sn3, value3, baseDN3, csn3); |
| | | handler.add(cn1, value1, baseDN1, csn1); |
| | | handler.add(cn2, value2, baseDN2, csn2); |
| | | handler.add(cn3, value3, baseDN3, csn3); |
| | | Thread.sleep(500); |
| | | |
| | | // Checks |
| | | assertEquals(handler.getFirstDraftCN(), sn1); |
| | | assertEquals(handler.getLastDraftCN(), sn3); |
| | | assertEquals(handler.getFirstChangeNumber(), cn1); |
| | | assertEquals(handler.getLastChangeNumber(), cn3); |
| | | |
| | | assertEquals(handler.count(), 3, "Db count"); |
| | | |
| | | assertEquals(handler.getPreviousCookie(sn1),value1); |
| | | assertEquals(handler.getPreviousCookie(sn2),value2); |
| | | assertEquals(handler.getPreviousCookie(sn3),value3); |
| | | assertEquals(handler.getPreviousCookie(cn1), value1); |
| | | assertEquals(handler.getPreviousCookie(cn2), value2); |
| | | assertEquals(handler.getPreviousCookie(cn3), value3); |
| | | |
| | | ChangeNumberIndexDBCursor cursor = handler.getCursorFrom(sn1); |
| | | assertCursorReadsInOrder(cursor, sn1, sn2, sn3); |
| | | ChangeNumberIndexDBCursor cursor = handler.getCursorFrom(cn1); |
| | | assertCursorReadsInOrder(cursor, cn1, cn2, cn3); |
| | | |
| | | cursor = handler.getCursorFrom(sn2); |
| | | assertCursorReadsInOrder(cursor, sn2, sn3); |
| | | cursor = handler.getCursorFrom(cn2); |
| | | assertCursorReadsInOrder(cursor, cn2, cn3); |
| | | |
| | | cursor = handler.getCursorFrom(sn3); |
| | | assertCursorReadsInOrder(cursor, sn3); |
| | | cursor = handler.getCursorFrom(cn3); |
| | | assertCursorReadsInOrder(cursor, cn3); |
| | | |
| | | handler.clear(); |
| | | |
| | | // Check the db is cleared. |
| | | assertEquals(handler.getFirstDraftCN(), 0); |
| | | assertEquals(handler.getLastDraftCN(), 0); |
| | | assertEquals(handler.getFirstChangeNumber(), 0); |
| | | assertEquals(handler.getLastChangeNumber(), 0); |
| | | assertEquals(handler.count(), 0); |
| | | } finally |
| | | } |
| | | finally |
| | | { |
| | | if (handler != null) |
| | | handler.shutdown(); |
| | |
| | | { |
| | | for (int i = 0; i < sns.length; i++) |
| | | { |
| | | assertEquals(cursor.getDraftCN(), sns[i]); |
| | | assertEquals(cursor.getChangeNumber(), sns[i]); |
| | | final boolean isNotLast = i + 1 < sns.length; |
| | | assertEquals(cursor.next(), isNotLast); |
| | | } |