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 */ 019package org.apache.directory.server.core.changelog; 020 021 022import java.util.ArrayList; 023import java.util.List; 024 025import org.apache.directory.api.ldap.model.constants.SchemaConstants; 026import org.apache.directory.api.ldap.model.entry.Attribute; 027import org.apache.directory.api.ldap.model.entry.DefaultEntry; 028import org.apache.directory.api.ldap.model.entry.Entry; 029import org.apache.directory.api.ldap.model.entry.Modification; 030import org.apache.directory.api.ldap.model.exception.LdapException; 031import org.apache.directory.api.ldap.model.ldif.ChangeType; 032import org.apache.directory.api.ldap.model.ldif.LdifEntry; 033import org.apache.directory.api.ldap.model.ldif.LdifRevertor; 034import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl; 035import org.apache.directory.api.ldap.model.name.Dn; 036import org.apache.directory.api.ldap.model.schema.AttributeType; 037import org.apache.directory.server.constants.ApacheSchemaConstants; 038import org.apache.directory.server.constants.ServerDNConstants; 039import org.apache.directory.server.core.api.CoreSession; 040import org.apache.directory.server.core.api.DirectoryService; 041import org.apache.directory.server.core.api.InterceptorEnum; 042import org.apache.directory.server.core.api.changelog.ChangeLog; 043import org.apache.directory.server.core.api.entry.ClonedServerEntry; 044import org.apache.directory.server.core.api.entry.ServerEntryUtils; 045import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 046import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 047import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 048import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 049import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 050import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 051import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 052import org.apache.directory.server.core.api.interceptor.context.OperationContext; 053import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 054import org.apache.directory.server.core.shared.SchemaService; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058 059/** 060 * An interceptor which intercepts write operations to the directory and 061 * logs them with the server's ChangeLog service. 062 * Note: Adding/deleting a tag is not recorded as a change 063 */ 064public class ChangeLogInterceptor extends BaseInterceptor 065{ 066 /** for debugging */ 067 private static final Logger LOG = LoggerFactory.getLogger( ChangeLogInterceptor.class ); 068 069 /** used to ignore modify operations to tombstone entries */ 070 private AttributeType entryDeleted; 071 072 /** the changelog service to log changes to */ 073 private ChangeLog changeLog; 074 075 /** OID of the 'rev' attribute used in changeLogEvent and tag objectclasses */ 076 private static final String REV_AT_OID = "1.3.6.1.4.1.18060.0.4.1.2.47"; 077 078 079 /** 080 * Creates a new instance of a ChangeLogInterceptor. 081 */ 082 public ChangeLogInterceptor() 083 { 084 super( InterceptorEnum.CHANGE_LOG_INTERCEPTOR ); 085 } 086 087 088 // ----------------------------------------------------------------------- 089 // Overridden init() and destroy() methods 090 // ----------------------------------------------------------------------- 091 /** 092 * The init method will initialize the local variables and load the 093 * entryDeleted AttributeType. 094 */ 095 @Override 096 public void init( DirectoryService directoryService ) throws LdapException 097 { 098 super.init( directoryService ); 099 100 changeLog = directoryService.getChangeLog(); 101 entryDeleted = directoryService.getSchemaManager() 102 .getAttributeType( ApacheSchemaConstants.ENTRY_DELETED_AT_OID ); 103 } 104 105 106 // ----------------------------------------------------------------------- 107 // Overridden (only change inducing) intercepted methods 108 // ----------------------------------------------------------------------- 109 /** 110 * {@inheritDoc} 111 */ 112 @Override 113 public void add( AddOperationContext addContext ) throws LdapException 114 { 115 next( addContext ); 116 117 if ( !changeLog.isEnabled() ) 118 { 119 return; 120 } 121 122 Entry addEntry = addContext.getEntry(); 123 124 // we don't want to record addition of a tag as a change 125 if ( addEntry.get( REV_AT_OID ) != null ) 126 { 127 return; 128 } 129 130 LdifEntry forward = new LdifEntry(); 131 forward.setChangeType( ChangeType.Add ); 132 forward.setDn( addContext.getDn() ); 133 134 for ( Attribute attribute : addEntry.getAttributes() ) 135 { 136 AttributeType attributeType = attribute.getAttributeType(); 137 forward.addAttribute( addEntry.get( attributeType ).clone() ); 138 } 139 140 LdifEntry reverse = LdifRevertor.reverseAdd( addContext.getDn() ); 141 addContext.setChangeLogEvent( changeLog.log( getPrincipal( addContext ), forward, reverse ) ); 142 } 143 144 145 /** 146 * The delete operation has to be stored with a way to restore the deleted element. 147 * There is no way to do that but reading the entry and dump it into the LOG. 148 */ 149 /** 150 * {@inheritDoc} 151 */ 152 @Override 153 public void delete( DeleteOperationContext deleteContext ) throws LdapException 154 { 155 // @todo make sure we're not putting in operational attributes that cannot be user modified 156 // must save the entry if change log is enabled 157 Entry serverEntry = null; 158 159 if ( changeLog.isEnabled() ) 160 { 161 serverEntry = getAttributes( deleteContext ); 162 } 163 164 next( deleteContext ); 165 166 if ( !changeLog.isEnabled() ) 167 { 168 return; 169 } 170 171 // we don't want to record deleting a tag as a change 172 if ( serverEntry.get( REV_AT_OID ) != null ) 173 { 174 return; 175 } 176 177 LdifEntry forward = new LdifEntry(); 178 forward.setChangeType( ChangeType.Delete ); 179 forward.setDn( deleteContext.getDn() ); 180 181 Entry reverseEntry = new DefaultEntry( serverEntry.getDn() ); 182 183 boolean isCollectiveSubentry = serverEntry.hasObjectClass( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ); 184 185 for ( Attribute attribute : serverEntry ) 186 { 187 // filter collective attributes, they can't be added by the revert operation 188 AttributeType at = schemaManager.lookupAttributeTypeRegistry( attribute.getId() ); 189 190 if ( !at.isCollective() || isCollectiveSubentry ) 191 { 192 reverseEntry.add( attribute.clone() ); 193 } 194 } 195 196 LdifEntry reverse = LdifRevertor.reverseDel( deleteContext.getDn(), reverseEntry ); 197 deleteContext.setChangeLogEvent( changeLog.log( getPrincipal( deleteContext ), forward, reverse ) ); 198 } 199 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public void modify( ModifyOperationContext modifyContext ) throws LdapException 206 { 207 Entry serverEntry = null; 208 Modification modification = ServerEntryUtils.getModificationItem( modifyContext.getModItems(), entryDeleted ); 209 boolean isDelete = modification != null; 210 211 if ( !isDelete && ( changeLog.isEnabled() ) ) 212 { 213 // @todo make sure we're not putting in operational attributes that cannot be user modified 214 serverEntry = getAttributes( modifyContext ); 215 } 216 217 // Duplicate modifications so that the reverse does not contain the operational attributes 218 List<Modification> clonedMods = new ArrayList<>(); 219 220 for ( Modification mod : modifyContext.getModItems() ) 221 { 222 clonedMods.add( mod.clone() ); 223 } 224 225 // Call the next interceptor 226 next( modifyContext ); 227 228 // @TODO: needs big consideration!!! 229 // NOTE: perhaps we need to log this as a system operation that cannot and should not be reapplied? 230 if ( isDelete 231 || !changeLog.isEnabled() 232 233 // if there are no modifications due to stripping out bogus non- 234 // existing attributes then we will have no modification items and 235 // should ignore not this without registering it with the changelog 236 237 || modifyContext.getModItems().isEmpty() ) 238 { 239 if ( isDelete ) 240 { 241 LOG.debug( "Bypassing changelog on modify of entryDeleted attribute." ); 242 } 243 244 return; 245 } 246 247 LdifEntry forward = new LdifEntry(); 248 forward.setChangeType( ChangeType.Modify ); 249 forward.setDn( modifyContext.getDn() ); 250 251 List<Modification> mods = new ArrayList<>( clonedMods.size() ); 252 253 for ( Modification modItem : clonedMods ) 254 { 255 // TODO: handle correctly http://issues.apache.org/jira/browse/DIRSERVER-1198 256 mods.add( modItem ); 257 258 forward.addModification( modItem ); 259 } 260 261 Entry clientEntry = new DefaultEntry( serverEntry.getDn() ); 262 263 for ( Attribute attribute : serverEntry ) 264 { 265 clientEntry.add( attribute.clone() ); 266 } 267 268 LdifEntry reverse = LdifRevertor.reverseModify( 269 modifyContext.getDn(), 270 mods, 271 clientEntry ); 272 273 modifyContext.setChangeLogEvent( changeLog.log( getPrincipal( modifyContext ), forward, reverse ) ); 274 } 275 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override 281 public void move( MoveOperationContext moveContext ) throws LdapException 282 { 283 next( moveContext ); 284 285 if ( !changeLog.isEnabled() ) 286 { 287 return; 288 } 289 290 LdifEntry forward = new LdifEntry(); 291 forward.setChangeType( ChangeType.ModDn ); 292 forward.setDn( moveContext.getDn() ); 293 forward.setNewSuperior( moveContext.getNewSuperior().getName() ); 294 295 LdifEntry reverse = LdifRevertor.reverseMove( moveContext.getNewSuperior(), moveContext.getDn() ); 296 moveContext.setChangeLogEvent( changeLog.log( getPrincipal( moveContext ), forward, reverse ) ); 297 } 298 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override 304 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 305 { 306 Entry serverEntry = null; 307 308 if ( changeLog.isEnabled() ) 309 { 310 // @todo make sure we're not putting in operational attributes that cannot be user modified 311 serverEntry = moveAndRenameContext.getOriginalEntry(); 312 } 313 314 next( moveAndRenameContext ); 315 316 if ( !changeLog.isEnabled() ) 317 { 318 return; 319 } 320 321 LdifEntry forward = new LdifEntry(); 322 forward.setChangeType( ChangeType.ModDn ); 323 forward.setDn( moveAndRenameContext.getDn() ); 324 forward.setDeleteOldRdn( moveAndRenameContext.getDeleteOldRdn() ); 325 forward.setNewRdn( moveAndRenameContext.getNewRdn().getName() ); 326 forward.setNewSuperior( moveAndRenameContext.getNewSuperiorDn().getName() ); 327 328 List<LdifEntry> reverses = LdifRevertor.reverseMoveAndRename( 329 serverEntry, moveAndRenameContext.getNewSuperiorDn(), moveAndRenameContext.getNewRdn(), false ); 330 331 if ( moveAndRenameContext.isReferralIgnored() ) 332 { 333 forward.addControl( new ManageDsaITImpl() ); 334 LdifEntry reversedEntry = reverses.get( 0 ); 335 reversedEntry.addControl( new ManageDsaITImpl() ); 336 } 337 338 moveAndRenameContext 339 .setChangeLogEvent( changeLog.log( getPrincipal( moveAndRenameContext ), forward, reverses ) ); 340 } 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override 347 public void rename( RenameOperationContext renameContext ) throws LdapException 348 { 349 Entry serverEntry = null; 350 351 if ( renameContext.getEntry() != null ) 352 { 353 serverEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry(); 354 } 355 356 next( renameContext ); 357 358 // After this point, the entry has been modified. The cloned entry contains 359 // the modified entry, the originalEntry has changed 360 361 if ( !changeLog.isEnabled() ) 362 { 363 return; 364 } 365 366 LdifEntry forward = new LdifEntry(); 367 forward.setChangeType( ChangeType.ModRdn ); 368 forward.setDn( renameContext.getDn() ); 369 forward.setNewRdn( renameContext.getNewRdn().getName() ); 370 forward.setDeleteOldRdn( renameContext.getDeleteOldRdn() ); 371 372 List<LdifEntry> reverses = LdifRevertor.reverseRename( 373 serverEntry, renameContext.getNewRdn(), renameContext.getDeleteOldRdn() ); 374 375 renameContext.setChangeLogEvent( changeLog.log( getPrincipal( renameContext ), forward, reverses ) ); 376 } 377 378 379 /** 380 * Gets attributes required for modifications. 381 * 382 * @param dn the dn of the entry to get 383 * @return the entry's attributes (may be immutable if the schema subentry) 384 * @throws Exception on error accessing the entry's attributes 385 */ 386 private Entry getAttributes( OperationContext opContext ) throws LdapException 387 { 388 Dn dn = opContext.getDn(); 389 Entry serverEntry; 390 391 // @todo make sure we're not putting in operational attributes that cannot be user modified 392 if ( dn.equals( ServerDNConstants.CN_SCHEMA_DN ) ) 393 { 394 return SchemaService.getSubschemaEntryCloned( directoryService ); 395 } 396 else 397 { 398 CoreSession session = opContext.getSession(); 399 LookupOperationContext lookupContext = new LookupOperationContext( session, dn, SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 400 lookupContext.setPartition( opContext.getPartition() ); 401 lookupContext.setTransaction( opContext.getTransaction() ); 402 403 serverEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 404 } 405 406 return serverEntry; 407 } 408}