001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2015 ForgeRock AS. 015 */ 016package org.opends.server.replication.server.changelog.file; 017 018import java.util.concurrent.atomic.AtomicReference; 019 020import org.opends.server.replication.common.CSN; 021import org.opends.server.replication.protocol.ReplicaOfflineMsg; 022import org.opends.server.replication.protocol.UpdateMsg; 023import org.opends.server.replication.server.changelog.api.ChangelogException; 024import org.opends.server.replication.server.changelog.api.DBCursor; 025import org.opends.server.replication.server.changelog.api.ReplicaId; 026import org.opends.server.replication.server.changelog.api.ReplicationDomainDB; 027 028/** 029 * {@link DBCursor} over a replica returning {@link UpdateMsg}s. 030 * <p> 031 * It decorates an existing {@link DBCursor} on a replicaDB and can possibly 032 * return replica offline messages when the decorated DBCursor is exhausted and 033 * the offline CSN is newer than the last returned update CSN. 034 */ 035public class ReplicaCursor implements DBCursor<UpdateMsg> 036{ 037 /** @NonNull */ 038 private final DBCursor<UpdateMsg> cursor; 039 private final AtomicReference<ReplicaOfflineMsg> replicaOfflineMsg = new AtomicReference<>(); 040 private UpdateMsg currentRecord; 041 042 private final ReplicaId replicaId; 043 private final ReplicationDomainDB domainDB; 044 045 /** 046 * Creates a ReplicaCursor object with a cursor to decorate 047 * and an offlineCSN to return as part of a ReplicaOfflineMsg. 048 * 049 * @param cursor 050 * the non-null underlying cursor that needs to be exhausted before 051 * we return a ReplicaOfflineMsg 052 * @param offlineCSN 053 * the offline CSN from which to builder the 054 * {@link ReplicaOfflineMsg} to return 055 * @param replicaId 056 * the replica identifier 057 * @param domainDB 058 * the DB for the provided replication domain 059 */ 060 public ReplicaCursor(DBCursor<UpdateMsg> cursor, CSN offlineCSN, ReplicaId replicaId, ReplicationDomainDB domainDB) 061 { 062 this.cursor = cursor; 063 this.replicaId = replicaId; 064 this.domainDB = domainDB; 065 setOfflineCSN(offlineCSN); 066 } 067 068 /** 069 * Sets the offline CSN to be returned by this cursor. 070 * 071 * @param offlineCSN 072 * The offline CSN to be returned by this cursor. 073 * If null, it will unset any previous offlineCSN and never return a ReplicaOfflineMsg 074 */ 075 public void setOfflineCSN(CSN offlineCSN) 076 { 077 this.replicaOfflineMsg.set( 078 offlineCSN != null ? new ReplicaOfflineMsg(offlineCSN) : null); 079 } 080 081 /** {@inheritDoc} */ 082 @Override 083 public UpdateMsg getRecord() 084 { 085 return currentRecord; 086 } 087 088 /** 089 * Returns the replica identifier that this cursor is associated to. 090 * 091 * @return the replica identifier that this cursor is associated to 092 */ 093 public ReplicaId getReplicaId() 094 { 095 return replicaId; 096 } 097 098 /** {@inheritDoc} */ 099 @Override 100 public boolean next() throws ChangelogException 101 { 102 final ReplicaOfflineMsg offlineMsg1 = replicaOfflineMsg.get(); 103 if (isReplicaOfflineMsgOutdated(offlineMsg1, currentRecord)) 104 { 105 replicaOfflineMsg.compareAndSet(offlineMsg1, null); 106 } 107 108 // now verify if new changes have been added to the DB 109 // (cursors are automatically restarted) 110 final UpdateMsg lastUpdate = cursor.getRecord(); 111 final boolean hasNext = cursor.next(); 112 if (hasNext) 113 { 114 currentRecord = cursor.getRecord(); 115 return true; 116 } 117 118 // replicaDB just happened to be exhausted now 119 final ReplicaOfflineMsg offlineMsg2 = replicaOfflineMsg.get(); 120 if (isReplicaOfflineMsgOutdated(offlineMsg2, lastUpdate)) 121 { 122 replicaOfflineMsg.compareAndSet(offlineMsg2, null); 123 currentRecord = null; 124 return false; 125 } 126 currentRecord = offlineMsg2; 127 return currentRecord != null; 128 } 129 130 /** It could also mean that the replica offline message has already been consumed. */ 131 private boolean isReplicaOfflineMsgOutdated( 132 final ReplicaOfflineMsg offlineMsg, final UpdateMsg updateMsg) 133 { 134 return offlineMsg != null 135 && updateMsg != null 136 && offlineMsg.getCSN().isOlderThanOrEqualTo(updateMsg.getCSN()); 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public void close() 142 { 143 cursor.close(); 144 domainDB.unregisterCursor(this); 145 } 146 147 /** {@inheritDoc} */ 148 @Override 149 public String toString() 150 { 151 final ReplicaOfflineMsg msg = replicaOfflineMsg.get(); 152 return getClass().getSimpleName() 153 + " currentRecord=" + currentRecord 154 + " offlineCSN=" + (msg != null ? msg.getCSN().toStringUI() : null) 155 + " cursor=" + cursor.toString().split("", 2)[1]; 156 } 157 158}