001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 021package org.apache.directory.server.ldap.replication.provider; 022 023 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.ConcurrentHashMap; 028 029import org.apache.directory.api.ldap.model.constants.Loggers; 030import org.apache.directory.api.ldap.model.constants.SchemaConstants; 031import org.apache.directory.api.ldap.model.cursor.Cursor; 032import org.apache.directory.api.ldap.model.entry.Attribute; 033import org.apache.directory.api.ldap.model.entry.DefaultEntry; 034import org.apache.directory.api.ldap.model.entry.DefaultModification; 035import org.apache.directory.api.ldap.model.entry.Entry; 036import org.apache.directory.api.ldap.model.entry.Modification; 037import org.apache.directory.api.ldap.model.entry.ModificationOperation; 038import org.apache.directory.api.ldap.model.entry.Value; 039import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException; 040import org.apache.directory.api.ldap.model.exception.LdapException; 041import org.apache.directory.api.ldap.model.filter.EqualityNode; 042import org.apache.directory.api.ldap.model.filter.ExprNode; 043import org.apache.directory.api.ldap.model.message.AliasDerefMode; 044import org.apache.directory.api.ldap.model.message.SearchRequest; 045import org.apache.directory.api.ldap.model.message.SearchRequestImpl; 046import org.apache.directory.api.ldap.model.message.SearchScope; 047import org.apache.directory.api.ldap.model.name.Dn; 048import org.apache.directory.api.ldap.model.schema.AttributeType; 049import org.apache.directory.api.ldap.model.schema.SchemaManager; 050import org.apache.directory.server.core.api.CoreSession; 051import org.apache.directory.server.core.api.DirectoryService; 052import org.apache.directory.server.core.api.event.EventType; 053import org.apache.directory.server.core.api.event.NotificationCriteria; 054import org.apache.directory.server.core.api.partition.Partition; 055import org.apache.directory.server.core.api.partition.PartitionTxn; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059 060/** 061 * Manage the consumers on the provider : add them, and remove them. 062 * 063 * All the consumers configuration will be stored in the 'ou=consumers,ou=system' branch. 064 * 065 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 066 */ 067public class ReplConsumerManager 068{ 069 /** Logger for this class */ 070 private static final Logger LOG = LoggerFactory.getLogger( ReplConsumerManager.class ); 071 072 /** A logger for the replication provider */ 073 private static final Logger PROVIDER_LOG = LoggerFactory.getLogger( Loggers.PROVIDER_LOG.getName() ); 074 075 /** The admin session used to commuicate with the backend */ 076 private CoreSession adminSession; 077 078 /** The DirectoryService instance */ 079 private DirectoryService directoryService; 080 081 /** The schema manager instance */ 082 private SchemaManager schemaManager; 083 084 /** The replication factory DN */ 085 private static final String REPL_CONSUMER_DN_STR = "ou=consumers,ou=system"; 086 private Dn replConsumerDn; 087 088 /** The consumers' ou value */ 089 private static final String CONSUMERS = "consumers"; 090 091 /** An AdsReplLastSentCsn AT instance */ 092 private AttributeType adsReplLastSentCsn; 093 094 /** A map containing the last sent CSN for every connected consumer */ 095 private Map<Integer, Modification> modMap = new ConcurrentHashMap<>(); 096 097 098 /** 099 * Create a new instance of the producer replication manager. 100 * 101 * @param directoryService The directoryService instance 102 * @throws Exception if we add an error while creating the configuration 103 */ 104 public ReplConsumerManager( DirectoryService directoryService ) throws Exception 105 { 106 this.directoryService = directoryService; 107 adminSession = directoryService.getAdminSession(); 108 schemaManager = directoryService.getSchemaManager(); 109 replConsumerDn = directoryService.getDnFactory().create( REPL_CONSUMER_DN_STR ); 110 adsReplLastSentCsn = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ADS_REPL_LAST_SENT_CSN ); 111 112 PROVIDER_LOG.debug( "Starting the replication consumer manager" ); 113 createConsumersBranch(); 114 } 115 116 117 /** 118 * Initialize the replication Store, creating the ou=consumers,ou=system entry (only if it does not exist yet) 119 */ 120 private void createConsumersBranch() throws Exception 121 { 122 if ( !adminSession.exists( replConsumerDn ) ) 123 { 124 LOG.debug( "creating the entry for storing replication consumers' details" ); 125 PROVIDER_LOG 126 .debug( "Creating the entry for storing replication consumers' details in {}", replConsumerDn ); 127 128 Entry entry = new DefaultEntry( schemaManager, replConsumerDn, 129 SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.ORGANIZATIONAL_UNIT_OC, 130 SchemaConstants.OU_AT, CONSUMERS ); 131 132 adminSession.add( entry ); 133 } 134 } 135 136 137 /** 138 * Add a new consumer entry in ou=consumers,ou=system 139 * 140 * @param replica The added consumer replica 141 * @throws Exception If the addition failed 142 */ 143 public void addConsumerEntry( ReplicaEventLog replica ) throws Exception 144 { 145 if ( replica == null ) 146 { 147 // No consumer ? Get out... 148 return; 149 } 150 151 PROVIDER_LOG.debug( "Adding a consumer for replica {}", replica ); 152 153 // Check that we don't already have an entry for this consumer 154 Dn consumerDn = directoryService.getDnFactory().create( 155 SchemaConstants.ADS_DS_REPLICA_ID + "=" + replica.getId() + "," + replConsumerDn ); 156 157 if ( adminSession.exists( consumerDn ) ) 158 { 159 // Error... 160 String message = "The replica " + consumerDn.getName() + " already exists"; 161 LOG.error( message ); 162 PROVIDER_LOG.error( message ); 163 throw new LdapEntryAlreadyExistsException( message ); 164 } 165 166 // Create the new consumer entry 167 Entry entry = new DefaultEntry( schemaManager, consumerDn, 168 SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.ADS_REPL_EVENT_LOG, 169 SchemaConstants.ADS_DS_REPLICA_ID, String.valueOf( replica.getId() ), 170 SchemaConstants.ADS_REPL_ALIAS_DEREF_MODE, replica.getSearchCriteria().getAliasDerefMode().getJndiValue(), 171 SchemaConstants.ADS_SEARCH_BASE_DN, replica.getSearchCriteria().getBase().getName(), 172 SchemaConstants.ADS_REPL_LAST_SENT_CSN, replica.getLastSentCsn(), 173 SchemaConstants.ADS_REPL_SEARCH_SCOPE, replica.getSearchCriteria().getScope().getLdapUrlValue(), 174 SchemaConstants.ADS_REPL_REFRESH_N_PERSIST, String.valueOf( replica.isRefreshNPersist() ), 175 SchemaConstants.ADS_REPL_SEARCH_FILTER, replica.getSearchFilter(), 176 SchemaConstants.ADS_REPL_LOG_MAX_IDLE, String.valueOf( replica.getMaxIdlePeriod() ), 177 SchemaConstants.ADS_REPL_LOG_PURGE_THRESHOLD_COUNT, String.valueOf( replica.getPurgeThresholdCount() ) ); 178 179 adminSession.add( entry ); 180 181 replica.setConsumerEntryDn( consumerDn ); 182 183 LOG.debug( "stored replication consumer entry {}", consumerDn ); 184 } 185 186 187 /** 188 * Delete an existing consumer entry from ou=consumers,ou=system 189 * 190 * @param replica The added consumer replica 191 * @throws LdapException If the addition failed 192 */ 193 public void deleteConsumerEntry( ReplicaEventLog replica ) throws LdapException 194 { 195 if ( replica == null ) 196 { 197 // No consumer ? Get out... 198 return; 199 } 200 201 // Check that we have an entry for this consumer 202 Dn consumerDn = directoryService.getDnFactory().create( 203 SchemaConstants.ADS_DS_REPLICA_ID + "=" + replica.getId() + "," + replConsumerDn ); 204 205 PROVIDER_LOG.debug( "Trying to delete the consumer entry {}", consumerDn ); 206 207 if ( !adminSession.exists( consumerDn ) ) 208 { 209 // Error... 210 String message = "The replica " + consumerDn.getName() + " does not exist"; 211 LOG.error( message ); 212 PROVIDER_LOG.debug( message ); 213 return; 214 } 215 216 // Delete the consumer entry 217 adminSession.delete( consumerDn ); 218 219 LOG.debug( "Deleted replication consumer entry {}", consumerDn ); 220 } 221 222 223 /** 224 * Store the new CSN sent by the consumer in place of the previous one. 225 * 226 * @param replica The consumer informations 227 * @throws Exception If the update failed 228 */ 229 public void updateReplicaLastSentCsn( ReplicaEventLog replica ) throws Exception 230 { 231 Modification mod = modMap.get( replica.getId() ); 232 Attribute lastSentCsnAt = null; 233 234 if ( mod == null ) 235 { 236 mod = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, adsReplLastSentCsn, 237 replica.getLastSentCsn() ); 238 239 modMap.put( replica.getId(), mod ); 240 } 241 else 242 { 243 lastSentCsnAt = mod.getAttribute(); 244 lastSentCsnAt.clear(); // clearing is mandatory 245 lastSentCsnAt.add( replica.getLastSentCsn() ); 246 } 247 248 Dn dn = directoryService.getDnFactory().create( 249 SchemaConstants.ADS_DS_REPLICA_ID + "=" + replica.getId() + "," + replConsumerDn ); 250 adminSession.modify( dn, mod ); 251 252 LOG.debug( "updated last sent CSN of consumer entry {}", dn ); 253 PROVIDER_LOG.debug( "updated the LastSentCSN of consumer entry {}", dn ); 254 } 255 256 257 /** 258 * Get the list of consumers' configuration 259 * 260 * @return A list of all the consumer configuration stored on the provider 261 * @throws Exception If we had an error while building this list 262 */ 263 public List<ReplicaEventLog> getReplicaEventLogs() throws Exception 264 { 265 List<ReplicaEventLog> replicas = new ArrayList<>(); 266 267 Partition partition = directoryService.getPartitionNexus().getPartition( replConsumerDn ); 268 269 270 // Search for all the consumers 271 ExprNode filter = new EqualityNode<String>( directoryService.getAtProvider().getObjectClass(), 272 new Value( directoryService.getAtProvider().getObjectClass(), SchemaConstants.ADS_REPL_EVENT_LOG ) ); 273 SearchRequest searchRequest = new SearchRequestImpl(); 274 searchRequest.setBase( replConsumerDn ); 275 searchRequest.setScope( SearchScope.ONELEVEL ); 276 searchRequest.setFilter( filter ); 277 searchRequest.addAttributes( SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 278 279 Cursor<Entry> cursor = adminSession.search( searchRequest ); 280 281 // Now loop on each consumer configuration 282 while ( cursor.next() ) 283 { 284 Entry entry = cursor.get(); 285 286 try ( PartitionTxn partitionTxn = partition.beginReadTransaction() ) 287 { 288 ReplicaEventLog replica = convertEntryToReplica( partitionTxn, entry ); 289 replicas.add( replica ); 290 } 291 } 292 293 cursor.close(); 294 295 // Now, we can return the list of replicas 296 return replicas; 297 } 298 299 300 /** 301 * Convert the stored entry to a valid ReplicaEventLog structure 302 */ 303 private ReplicaEventLog convertEntryToReplica( PartitionTxn partitionTxn, Entry entry ) throws Exception 304 { 305 String id = entry.get( SchemaConstants.ADS_DS_REPLICA_ID ).getString(); 306 ReplicaEventLog replica = new ReplicaEventLog( partitionTxn, directoryService, Integer.parseInt( id ) ); 307 308 NotificationCriteria searchCriteria = new NotificationCriteria( schemaManager ); 309 310 String aliasMode = entry.get( SchemaConstants.ADS_REPL_ALIAS_DEREF_MODE ).getString(); 311 searchCriteria.setAliasDerefMode( AliasDerefMode.getDerefMode( aliasMode ) ); 312 313 String baseDn = entry.get( SchemaConstants.ADS_SEARCH_BASE_DN ).getString(); 314 searchCriteria.setBase( new Dn( schemaManager, baseDn ) ); 315 316 Attribute lastSentCsnAt = entry.get( SchemaConstants.ADS_REPL_LAST_SENT_CSN ); 317 318 if ( lastSentCsnAt != null ) 319 { 320 replica.setLastSentCsn( lastSentCsnAt.getString() ); 321 } 322 323 String scope = entry.get( SchemaConstants.ADS_REPL_SEARCH_SCOPE ).getString(); 324 int scopeIntVal = SearchScope.getSearchScope( scope ); 325 searchCriteria.setScope( SearchScope.getSearchScope( scopeIntVal ) ); 326 327 String filter = entry.get( SchemaConstants.ADS_REPL_SEARCH_FILTER ).getString(); 328 searchCriteria.setFilter( filter ); 329 replica.setSearchFilter( filter ); 330 331 replica.setRefreshNPersist( Boolean.parseBoolean( entry.get( SchemaConstants.ADS_REPL_REFRESH_N_PERSIST ) 332 .getString() ) ); 333 334 searchCriteria.setEventMask( EventType.ALL_EVENT_TYPES_MASK ); 335 replica.setSearchCriteria( searchCriteria ); 336 337 int maxIdlePeriod = Integer.parseInt( entry.get( SchemaConstants.ADS_REPL_LOG_MAX_IDLE ).getString() ); 338 replica.setMaxIdlePeriod( maxIdlePeriod ); 339 340 int purgeThreshold = Integer.parseInt( entry.get( SchemaConstants.ADS_REPL_LOG_PURGE_THRESHOLD_COUNT ) 341 .getString() ); 342 replica.setPurgeThresholdCount( purgeThreshold ); 343 344 // explicitly mark the replica as not-dirty, cause we just loaded it from 345 // the store, this prevents updating the replica info immediately after loading 346 replica.setDirty( false ); 347 348 replica.setConsumerEntryDn( entry.getDn() ); 349 350 return replica; 351 } 352}