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.journal;
020
021
022import java.util.concurrent.atomic.AtomicLong;
023
024import org.apache.directory.api.ldap.model.entry.Attribute;
025import org.apache.directory.api.ldap.model.entry.Entry;
026import org.apache.directory.api.ldap.model.entry.Modification;
027import org.apache.directory.api.ldap.model.exception.LdapException;
028import org.apache.directory.api.ldap.model.ldif.ChangeType;
029import org.apache.directory.api.ldap.model.ldif.LdifEntry;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.server.core.api.DirectoryService;
032import org.apache.directory.server.core.api.InterceptorEnum;
033import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
034import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
035import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
036import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
037import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
038import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
039import org.apache.directory.server.core.api.interceptor.context.OperationContext;
040import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
041import org.apache.directory.server.core.api.journal.Journal;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045
046/**
047 * An interceptor which intercepts write operations to the directory and
048 * logs them into a journal.
049 * 
050 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
051 */
052public class JournalInterceptor extends BaseInterceptor
053{
054    /** for debugging */
055    private static final Logger LOG = LoggerFactory.getLogger( JournalInterceptor.class );
056
057    /** A flag set to true if the journal interceptor is enabled */
058    private boolean journalEnabled;
059
060    /** A shared number stored within each change */
061    private AtomicLong revision;
062
063    /** the Journal service to log changes to */
064    private Journal journal;
065
066
067    /**
068     * Creates a new instance of a JournalInterceptor.
069     */
070    public JournalInterceptor()
071    {
072        super( InterceptorEnum.JOURNAL_INTERCEPTOR );
073    }
074
075
076    // -----------------------------------------------------------------------
077    // Overridden init() and destroy() methods
078    // -----------------------------------------------------------------------
079    /**
080     * The init method will initialize the local variables and load the
081     * entryDeleted AttributeType.
082     */
083    @Override
084    public void init( DirectoryService directoryService ) throws LdapException
085    {
086        super.init( directoryService );
087
088        if ( directoryService.getJournal().isEnabled() )
089        {
090            journalEnabled = true;
091            journal = directoryService.getJournal();
092            revision = new AtomicLong( System.currentTimeMillis() );
093        }
094
095        LOG.debug( "JournalInterceptor has been initialized" );
096    }
097
098
099    /**
100     * Log the operation, manage the logs rotations.
101     */
102    private void log( OperationContext opCtx, long revision, LdifEntry ldif ) throws LdapException
103    {
104        journal.log( getPrincipal( opCtx ), revision, ldif );
105    }
106
107
108    // -----------------------------------------------------------------------
109    // Overridden (only change inducing) intercepted methods
110    // -----------------------------------------------------------------------
111    /**
112     * {@inheritDoc}
113     */
114    @Override
115    public void add( AddOperationContext addContext ) throws LdapException
116    {
117        long opRevision = 0;
118
119        if ( journalEnabled )
120        {
121            opRevision = revision.incrementAndGet();
122
123            // Store the added entry
124            Entry addEntry = addContext.getEntry();
125
126            LdifEntry ldif = new LdifEntry();
127            ldif.setChangeType( ChangeType.Add );
128            ldif.setDn( addContext.getDn() );
129
130            for ( Attribute attribute : addEntry.getAttributes() )
131            {
132                AttributeType attributeType = attribute.getAttributeType();
133                ldif.addAttribute( addEntry.get( attributeType ).clone() );
134            }
135
136            log( addContext, opRevision, ldif );
137        }
138
139        try
140        {
141            next( addContext );
142
143            if ( journalEnabled )
144            {
145                // log the ACK
146                journal.ack( opRevision );
147            }
148        }
149        catch ( LdapException le )
150        {
151            if ( journalEnabled )
152            {
153                // log the NACK
154                journal.nack( opRevision );
155            }
156
157            throw le;
158        }
159    }
160
161
162    /**
163     * {@inheritDoc}
164     */
165    @Override
166    public void delete( DeleteOperationContext deleteContext ) throws LdapException
167    {
168        long opRevision = 0;
169
170        if ( journalEnabled )
171        {
172            opRevision = revision.incrementAndGet();
173
174            // Store the deleted entry
175            LdifEntry ldif = new LdifEntry();
176            ldif.setChangeType( ChangeType.Delete );
177            ldif.setDn( deleteContext.getDn() );
178
179            journal.log( getPrincipal( deleteContext ), opRevision, ldif );
180        }
181
182        try
183        {
184            next( deleteContext );
185
186            if ( journalEnabled )
187            {
188                // log the ACK
189                journal.ack( opRevision );
190            }
191        }
192        catch ( LdapException e )
193        {
194            if ( journalEnabled )
195            {
196                // log the NACK
197                journal.nack( opRevision );
198            }
199
200            throw e;
201        }
202    }
203
204
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public void modify( ModifyOperationContext modifyContext ) throws LdapException
210    {
211        long opRevision = 0;
212
213        if ( journalEnabled )
214        {
215            opRevision = revision.incrementAndGet();
216
217            // Store the modified entry
218            LdifEntry ldif = new LdifEntry();
219            ldif.setChangeType( ChangeType.Modify );
220            ldif.setDn( modifyContext.getDn() );
221
222            // Store the modifications
223            for ( Modification modification : modifyContext.getModItems() )
224            {
225                ldif.addModification( modification );
226            }
227
228            journal.log( getPrincipal( modifyContext ), opRevision, ldif );
229        }
230
231        try
232        {
233            next( modifyContext );
234
235            if ( journalEnabled )
236            {
237                // log the ACK
238                journal.ack( opRevision );
239            }
240        }
241        catch ( LdapException e )
242        {
243            if ( journalEnabled )
244            {
245                // log the NACK
246                journal.nack( opRevision );
247            }
248            throw e;
249        }
250    }
251
252
253    /**
254     * {@inheritDoc}
255     */
256    @Override
257    public void move( MoveOperationContext moveContext ) throws LdapException
258    {
259        long opRevision = 0;
260
261        if ( journalEnabled )
262        {
263            opRevision = revision.incrementAndGet();
264
265            // Store the moved entry
266            LdifEntry ldif = new LdifEntry();
267            ldif.setChangeType( ChangeType.ModDn );
268            ldif.setDn( moveContext.getDn() );
269            ldif.setNewSuperior( moveContext.getNewSuperior().getName() );
270
271            journal.log( getPrincipal( moveContext ), opRevision, ldif );
272        }
273
274        try
275        {
276            next( moveContext );
277
278            if ( journalEnabled )
279            {
280                // log the ACK
281                journal.ack( opRevision );
282            }
283        }
284        catch ( LdapException e )
285        {
286            if ( journalEnabled )
287            {
288                // log the NACK
289                journal.nack( opRevision );
290            }
291
292            throw e;
293        }
294    }
295
296
297    /**
298     * {@inheritDoc}
299     */
300    @Override
301    public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
302    {
303        long opRevision = 0;
304
305        if ( journalEnabled )
306        {
307            opRevision = revision.incrementAndGet();
308
309            // Store the renamed entry
310            LdifEntry ldif = new LdifEntry();
311            ldif.setChangeType( ChangeType.ModDn );
312            ldif.setDn( moveAndRenameContext.getDn() );
313            ldif.setNewRdn( moveAndRenameContext.getNewRdn().getName() );
314            ldif.setDeleteOldRdn( moveAndRenameContext.getDeleteOldRdn() );
315            ldif.setNewSuperior( moveAndRenameContext.getNewDn().getName() );
316
317            journal.log( getPrincipal( moveAndRenameContext ), opRevision, ldif );
318        }
319
320        try
321        {
322            next( moveAndRenameContext );
323
324            if ( journalEnabled )
325            {
326                // log the ACK
327                journal.ack( opRevision );
328            }
329        }
330        catch ( LdapException e )
331        {
332            if ( journalEnabled )
333            {
334                // log the NACK
335                journal.nack( opRevision );
336            }
337
338            throw e;
339        }
340    }
341
342
343    /**
344     * {@inheritDoc}
345     */
346    @Override
347    public void rename( RenameOperationContext renameContext ) throws LdapException
348    {
349        long opRevision = 0;
350
351        if ( journalEnabled )
352        {
353            opRevision = revision.incrementAndGet();
354
355            // Store the renamed entry
356            LdifEntry ldif = new LdifEntry();
357            ldif.setChangeType( ChangeType.ModRdn );
358            ldif.setDn( renameContext.getDn() );
359            ldif.setNewRdn( renameContext.getNewRdn().getName() );
360            ldif.setDeleteOldRdn( renameContext.getDeleteOldRdn() );
361
362            journal.log( getPrincipal( renameContext ), opRevision, ldif );
363        }
364
365        try
366        {
367            next( renameContext );
368
369            if ( journalEnabled )
370            {
371                // log the ACK
372                journal.ack( opRevision );
373            }
374        }
375        catch ( LdapException e )
376        {
377            if ( journalEnabled )
378            {
379                // log the NACK
380                journal.nack( opRevision );
381            }
382
383            throw e;
384        }
385    }
386}