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    }