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  import java.util.Set;
25  
26  import org.apache.directory.server.constants.ApacheSchemaConstants;
27  import org.apache.directory.server.core.DirectoryService;
28  import org.apache.directory.server.core.entry.ClonedServerEntry;
29  import org.apache.directory.server.core.entry.ServerAttribute;
30  import org.apache.directory.server.core.entry.ServerEntry;
31  import org.apache.directory.server.core.entry.ServerEntryUtils;
32  import org.apache.directory.server.core.entry.ServerModification;
33  import org.apache.directory.server.core.interceptor.BaseInterceptor;
34  import org.apache.directory.server.core.interceptor.NextInterceptor;
35  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
36  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
37  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
38  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
39  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
40  import org.apache.directory.server.core.interceptor.context.OperationContext;
41  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
42  import org.apache.directory.server.core.partition.ByPassConstants;
43  import org.apache.directory.server.core.schema.SchemaService;
44  import org.apache.directory.shared.ldap.entry.Entry;
45  import org.apache.directory.shared.ldap.entry.EntryAttribute;
46  import org.apache.directory.shared.ldap.entry.Modification;
47  import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
48  import org.apache.directory.shared.ldap.ldif.ChangeType;
49  import org.apache.directory.shared.ldap.ldif.LdifEntry;
50  import org.apache.directory.shared.ldap.ldif.LdifUtils;
51  import org.apache.directory.shared.ldap.name.LdapDN;
52  import org.apache.directory.shared.ldap.name.Rdn;
53  import org.apache.directory.shared.ldap.schema.AttributeType;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  
58  /**
59   * An interceptor which intercepts write operations to the directory and
60   * logs them with the server's ChangeLog service.
61   * Note: Adding/deleting a tag is not recorded as a change
62   */
63  public class ChangeLogInterceptor extends BaseInterceptor
64  {
65      /** for debugging */
66      private static final Logger LOG = LoggerFactory.getLogger( ChangeLogInterceptor.class );
67      
68      /** used to ignore modify operations to tombstone entries */
69      private AttributeType entryDeleted;
70      
71      /** the changelog service to log changes to */
72      private ChangeLog changeLog;
73      
74      /** we need the schema service to deal with special conditions */
75      private SchemaService schemaService;
76  
77      /** OID of the 'rev' attribute used in changeLogEvent and tag objectclasses */
78      private static final String REV_OID = "1.3.6.1.4.1.18060.0.4.1.2.47";
79      
80      // -----------------------------------------------------------------------
81      // Overridden init() and destroy() methods
82      // -----------------------------------------------------------------------
83  
84  
85      public void init( DirectoryService directoryService ) throws Exception
86      {
87          super.init( directoryService );
88  
89          changeLog = directoryService.getChangeLog();
90          schemaService = directoryService.getSchemaService();
91          entryDeleted = directoryService.getRegistries().getAttributeTypeRegistry()
92                  .lookup( ApacheSchemaConstants.ENTRY_DELETED_OID );
93      }
94  
95  
96      // -----------------------------------------------------------------------
97      // Overridden (only change inducing) intercepted methods
98      // -----------------------------------------------------------------------
99      
100 
101     public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
102     {
103         next.add( opContext );
104 
105         if ( ! changeLog.isEnabled() || ! opContext.isFirstOperation() )
106         {
107             return;
108         }
109 
110         ServerEntry addEntry = opContext.getEntry();
111 
112         // we don't want to record addition of a tag as a change
113         if( addEntry.get( REV_OID ) != null )
114         {
115            return; 
116         }
117         
118         LdifEntry forward = new LdifEntry();
119         forward.setChangeType( ChangeType.Add );
120         forward.setDn( opContext.getDn() );
121 
122         Set<AttributeType> list = addEntry.getAttributeTypes();
123         
124         for ( AttributeType attributeType:list )
125         {
126             forward.addAttribute( ((ServerAttribute)addEntry.get( attributeType) ).toClientAttribute() );
127         }
128         
129         LdifEntry reverse = LdifUtils.reverseAdd( opContext.getDn() );
130         opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
131     }
132 
133 
134     /**
135      * The delete operation has to be stored with a way to restore the deleted element.
136      * There is no way to do that but reading the entry and dump it into the LOG.
137      */
138     public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
139     {
140         // @todo make sure we're not putting in operational attributes that cannot be user modified
141         // must save the entry if change log is enabled
142         ServerEntry serverEntry = null;
143 
144         if ( changeLog.isEnabled() && opContext.isFirstOperation() )
145         {
146             serverEntry = getAttributes( opContext );
147         }
148 
149         next.delete( opContext );
150 
151         if ( ! changeLog.isEnabled() || ! opContext.isFirstOperation() )
152         {
153             return;
154         }
155 
156         // we don't want to record deleting a tag as a change
157         if( serverEntry.get( REV_OID ) != null )
158         {
159            return; 
160         }
161 
162         LdifEntry forward = new LdifEntry();
163         forward.setChangeType( ChangeType.Delete );
164         forward.setDn( opContext.getDn() );
165         
166         Entry reverseEntry = new DefaultClientEntry( serverEntry.getDn() );
167         
168         for ( EntryAttribute attribute:serverEntry )
169         {
170             reverseEntry.add( ((ServerAttribute)attribute).toClientAttribute() );
171         }
172 
173         LdifEntry reverse = LdifUtils.reverseDel( opContext.getDn(), reverseEntry );
174         opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
175     }
176 
177 
178     /**
179      * Gets attributes required for modifications.
180      *
181      * @param dn the dn of the entry to get
182      * @return the entry's attributes (may be immutable if the schema subentry)
183      * @throws Exception on error accessing the entry's attributes
184      */
185     private ServerEntry getAttributes( OperationContext opContext ) throws Exception
186     {
187         LdapDN dn = opContext.getDn();
188         ClonedServerEntry serverEntry;
189 
190         // @todo make sure we're not putting in operational attributes that cannot be user modified
191         if ( schemaService.isSchemaSubentry( dn.toNormName() ) )
192         {
193             return schemaService.getSubschemaEntryCloned();
194         }
195         else
196         {
197             serverEntry = opContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS );
198         }
199 
200         return serverEntry;
201     }
202 
203 
204     /**
205      * 
206      */
207     public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
208     {
209         ServerEntry serverEntry = null;
210         Modification modification = ServerEntryUtils.getModificationItem( opContext.getModItems(), entryDeleted );
211         boolean isDelete = ( modification != null );
212 
213         if ( ! isDelete && ( changeLog.isEnabled() && opContext.isFirstOperation() ) )
214         {
215             // @todo make sure we're not putting in operational attributes that cannot be user modified
216             serverEntry = getAttributes( opContext );
217         }
218 
219         next.modify( opContext );
220 
221         // @TODO: needs big consideration!!!
222         // NOTE: perhaps we need to log this as a system operation that cannot and should not be reapplied?
223         if ( 
224             isDelete ||   
225             ! changeLog.isEnabled() || 
226             ! opContext.isFirstOperation() ||
227             
228          // if there are no modifications due to stripping out bogus non-
229          // existing attributes then we will have no modification items and
230          // should ignore not this without registerring it with the changelog
231          
232             opContext.getModItems().size() == 0 )  
233         {
234             if ( isDelete )
235             {
236                 LOG.debug( "Bypassing changelog on modify of entryDeleted attribute." );
237             }
238             
239             return;
240         }
241 
242         LdifEntry forward = new LdifEntry();
243         forward.setChangeType( ChangeType.Modify );
244         forward.setDn( opContext.getDn() );
245         
246         List<Modification> mods = new ArrayList<Modification>( opContext.getModItems().size() );
247         
248         for ( Modification modItem : opContext.getModItems() )
249         {
250             Modification mod = ((ServerModification)modItem).toClientModification();
251             
252             // TODO: handle correctly http://issues.apache.org/jira/browse/DIRSERVER-1198
253             mod.getAttribute().setId( modItem.getAttribute().getId() );
254             mods.add( mod );
255             
256             forward.addModificationItem( mod );
257         }
258         
259         Entry clientEntry = new DefaultClientEntry( serverEntry.getDn() );
260         
261         for ( EntryAttribute attribute:serverEntry )
262         {
263             clientEntry.add( ((ServerAttribute)attribute).toClientAttribute() );
264         }
265 
266         LdifEntry reverse = LdifUtils.reverseModify( 
267             opContext.getDn(), 
268             mods, 
269             clientEntry );
270         
271         opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
272     }
273 
274 
275     // -----------------------------------------------------------------------
276     // Though part left as an exercise (Not Any More!)
277     // -----------------------------------------------------------------------
278 
279 
280     public void rename ( NextInterceptor next, RenameOperationContext renameContext ) throws Exception
281     {
282         ServerEntry serverEntry = null;
283         
284         if ( changeLog.isEnabled() && renameContext.isFirstOperation() )
285         {
286             // @todo make sure we're not putting in operational attributes that cannot be user modified
287             serverEntry = getAttributes( renameContext );
288         }
289 
290         next.rename( renameContext );
291 
292         if ( ! changeLog.isEnabled() || ! renameContext.isFirstOperation() )
293         {
294             return;
295         }
296 
297         LdifEntry forward = new LdifEntry();
298         forward.setChangeType( ChangeType.ModRdn );
299         forward.setDn( renameContext.getDn() );
300         forward.setNewRdn( renameContext.getNewRdn().getUpName() );
301         forward.setDeleteOldRdn( renameContext.getDelOldDn() );
302 
303         List<LdifEntry> reverses = LdifUtils.reverseModifyRdn( ServerEntryUtils.toBasicAttributes( serverEntry ), 
304             null, renameContext.getDn(), new Rdn( renameContext.getNewRdn() ) );
305         
306         renameContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverses ) );
307     }
308 
309 
310     public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opCtx )
311         throws Exception
312     {
313         ClonedServerEntry serverEntry = null;
314         
315         if ( changeLog.isEnabled() && opCtx.isFirstOperation() )
316         {
317             // @todo make sure we're not putting in operational attributes that cannot be user modified
318             serverEntry = opCtx.lookup( opCtx.getDn(), ByPassConstants.LOOKUP_BYPASS );
319         }
320 
321         next.moveAndRename( opCtx );
322 
323         if ( ! changeLog.isEnabled() || ! opCtx.isFirstOperation() )
324         {
325             return;
326         }
327 
328         LdifEntry forward = new LdifEntry();
329         forward.setChangeType( ChangeType.ModDn );
330         forward.setDn( opCtx.getDn() );
331         forward.setDeleteOldRdn( opCtx.getDelOldDn() );
332         forward.setNewRdn( opCtx.getNewRdn().getUpName() );
333         forward.setNewSuperior( opCtx.getParent().getUpName() );
334 
335         List<LdifEntry> reverses = LdifUtils.reverseModifyRdn( ServerEntryUtils.toBasicAttributes( serverEntry ), 
336             opCtx.getParent(), opCtx.getDn(), new Rdn( opCtx.getNewRdn() ) );
337         opCtx.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverses ) );
338     }
339 
340 
341     public void move ( NextInterceptor next, MoveOperationContext opCtx ) throws Exception
342     {
343         next.move( opCtx );
344 
345         if ( ! changeLog.isEnabled() || ! opCtx.isFirstOperation() )
346         {
347             return;
348         }
349 
350         LdifEntry forward = new LdifEntry();
351         forward.setChangeType( ChangeType.ModDn );
352         forward.setDn( opCtx.getDn() );
353         forward.setNewSuperior( opCtx.getParent().getUpName() );
354 
355         LdifEntry reverse = LdifUtils.reverseModifyDn( opCtx.getParent(), opCtx.getDn() );
356         opCtx.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
357     }
358 }