View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.server.core.changelog;
20  
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
26  import org.apache.directory.api.ldap.model.entry.Attribute;
27  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
28  import org.apache.directory.api.ldap.model.entry.Entry;
29  import org.apache.directory.api.ldap.model.entry.Modification;
30  import org.apache.directory.api.ldap.model.exception.LdapException;
31  import org.apache.directory.api.ldap.model.ldif.ChangeType;
32  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
33  import org.apache.directory.api.ldap.model.ldif.LdifRevertor;
34  import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl;
35  import org.apache.directory.api.ldap.model.name.Dn;
36  import org.apache.directory.api.ldap.model.schema.AttributeType;
37  import org.apache.directory.server.constants.ApacheSchemaConstants;
38  import org.apache.directory.server.constants.ServerDNConstants;
39  import org.apache.directory.server.core.api.CoreSession;
40  import org.apache.directory.server.core.api.DirectoryService;
41  import org.apache.directory.server.core.api.InterceptorEnum;
42  import org.apache.directory.server.core.api.changelog.ChangeLog;
43  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
44  import org.apache.directory.server.core.api.entry.ServerEntryUtils;
45  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
46  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
47  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
48  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
51  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
52  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
53  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
54  import org.apache.directory.server.core.shared.SchemaService;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  
59  /**
60   * An interceptor which intercepts write operations to the directory and
61   * logs them with the server's ChangeLog service.
62   * Note: Adding/deleting a tag is not recorded as a change
63   */
64  public class ChangeLogInterceptor extends BaseInterceptor
65  {
66      /** for debugging */
67      private static final Logger LOG = LoggerFactory.getLogger( ChangeLogInterceptor.class );
68  
69      /** used to ignore modify operations to tombstone entries */
70      private AttributeType entryDeleted;
71  
72      /** the changelog service to log changes to */
73      private ChangeLog changeLog;
74  
75      /** OID of the 'rev' attribute used in changeLogEvent and tag objectclasses */
76      private static final String REV_AT_OID = "1.3.6.1.4.1.18060.0.4.1.2.47";
77  
78  
79      /**
80       * Creates a new instance of a ChangeLogInterceptor.
81       */
82      public ChangeLogInterceptor()
83      {
84          super( InterceptorEnum.CHANGE_LOG_INTERCEPTOR );
85      }
86  
87  
88      // -----------------------------------------------------------------------
89      // Overridden init() and destroy() methods
90      // -----------------------------------------------------------------------
91      /**
92       * The init method will initialize the local variables and load the
93       * entryDeleted AttributeType.
94       */
95      @Override
96      public void init( DirectoryService directoryService ) throws LdapException
97      {
98          super.init( directoryService );
99  
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             LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">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 }