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