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     */
020    package org.apache.directory.server.ldap.handlers;
021    
022    
023    import javax.naming.NamingException;
024    
025    import org.apache.directory.server.core.event.DirectoryListener;
026    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
027    import org.apache.directory.server.core.interceptor.context.ChangeOperationContext;
028    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
029    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
030    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
031    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
032    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
033    import org.apache.directory.server.i18n.I18n;
034    import org.apache.directory.server.ldap.LdapSession;
035    import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
036    import org.apache.directory.shared.ldap.codec.search.controls.entryChange.EntryChangeControl;
037    import org.apache.directory.shared.ldap.codec.search.controls.persistentSearch.PersistentSearchControl;
038    import org.apache.directory.shared.ldap.message.AbandonListener;
039    import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl;
040    import org.apache.directory.shared.ldap.message.internal.InternalAbandonableRequest;
041    import org.apache.directory.shared.ldap.message.internal.InternalSearchRequest;
042    import org.apache.directory.shared.ldap.message.internal.InternalSearchResponseEntry;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    
047    /**
048     * A DirectoryListener implementation which sends back added, deleted, modified or 
049     * renamed entries to a client that created this listener.  This class is part of the
050     * persistent search implementation which uses the event notification scheme built into
051     * the server core.  
052     * 
053     * This listener is disabled only when a session closes or when an abandon request 
054     * cancels it.  Hence time and size limits in normal search operations do not apply
055     * here.
056     * 
057     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
058     * @version $Rev: 905344 $
059     */
060    public class PersistentSearchListener implements DirectoryListener, AbandonListener
061    {
062        private static final Logger LOG = LoggerFactory.getLogger( PersistentSearchListener.class );
063        final LdapSession session;
064        final InternalSearchRequest req;
065        final PersistentSearchControl control;
066    
067    
068        PersistentSearchListener( LdapSession session, InternalSearchRequest req )
069        {
070            this.session = session;
071            this.req = req;
072            req.addAbandonListener( this );
073            this.control = ( PersistentSearchControl ) req.getControls().get( PersistentSearchControl.CONTROL_OID );
074        }
075    
076    
077        public void abandon() throws NamingException
078        {
079            // must abandon the operation 
080            session.getCoreSession().getDirectoryService().getEventService().removeListener( this );
081    
082            /*
083             * From RFC 2251 Section 4.11:
084             * 
085             * In the event that a server receives an Abandon Request on a Search  
086             * operation in the midst of transmitting responses to the Search, that
087             * server MUST cease transmitting entry responses to the abandoned
088             * request immediately, and MUST NOT send the SearchResultDone. Of
089             * course, the server MUST ensure that only properly encoded LDAPMessage
090             * PDUs are transmitted. 
091             * 
092             * SO DON'T SEND BACK ANYTHING!!!!!
093             */
094        }
095    
096        
097        public void requestAbandoned( InternalAbandonableRequest req )
098        {
099            try
100            {
101                abandon();
102            }
103            catch ( NamingException e )
104            {
105                LOG.error( I18n.err( I18n.ERR_164 ), e );
106            }
107        }
108        
109        
110        private void setECResponseControl( InternalSearchResponseEntry response, ChangeOperationContext opContext, ChangeType type )
111        {
112            if ( control.isReturnECs() )
113            {
114                EntryChangeControl ecControl = new EntryChangeControl();
115                ecControl.setChangeType( type );
116                
117                if ( opContext.getChangeLogEvent() != null )
118                {
119                    ecControl.setChangeNumber( opContext.getChangeLogEvent().getRevision() );
120                }
121             
122                if ( opContext instanceof RenameOperationContext || opContext instanceof MoveOperationContext )
123                {
124                    ecControl.setPreviousDn( opContext.getDn() ); 
125                }
126                
127                response.add( ecControl );
128            }
129        }
130    
131    
132        public void entryAdded( AddOperationContext opContext )
133        {
134            if ( ! control.isNotificationEnabled( ChangeType.ADD ) )
135            {
136                return;
137            }
138        
139            InternalSearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
140            respEntry.setObjectName( opContext.getDn() );
141            respEntry.setEntry( opContext.getEntry() );
142            setECResponseControl( respEntry, opContext, ChangeType.ADD );
143            session.getIoSession().write( respEntry );
144        }
145    
146    
147        public void entryDeleted( DeleteOperationContext opContext )
148        {
149            if ( ! control.isNotificationEnabled( ChangeType.DELETE ) )
150            {
151                return;
152            }
153        
154            InternalSearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
155            respEntry.setObjectName( opContext.getDn() );
156            respEntry.setEntry( opContext.getEntry() );
157            setECResponseControl( respEntry, opContext, ChangeType.DELETE );
158            session.getIoSession().write( respEntry );
159        }
160    
161    
162        public void entryModified( ModifyOperationContext opContext )
163        {
164            if ( ! control.isNotificationEnabled( ChangeType.MODIFY ) )
165            {
166                return;
167            }
168        
169            InternalSearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
170            respEntry.setObjectName( opContext.getDn() );
171            respEntry.setEntry( opContext.getAlteredEntry() );
172            setECResponseControl( respEntry, opContext, ChangeType.MODIFY );
173            session.getIoSession().write( respEntry );
174        }
175    
176    
177        public void entryMoved( MoveOperationContext opContext )
178        {
179            if ( ! control.isNotificationEnabled( ChangeType.MODDN ) )
180            {
181                return;
182            }
183        
184            InternalSearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
185            respEntry.setObjectName( opContext.getDn() );
186            respEntry.setEntry( opContext.getEntry() );
187            setECResponseControl( respEntry, opContext, ChangeType.MODDN );
188            session.getIoSession().write( respEntry );
189        }
190    
191    
192        public void entryMovedAndRenamed( MoveAndRenameOperationContext opContext )
193        {
194            entryRenamed( opContext );
195        }
196    
197    
198        public void entryRenamed( RenameOperationContext opContext )
199        {
200            if ( ! control.isNotificationEnabled( ChangeType.MODDN ) )
201            {
202                return;
203            }
204        
205            InternalSearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
206            respEntry.setObjectName( opContext.getAlteredEntry().getDn() );
207            respEntry.setEntry( opContext.getAlteredEntry() );
208            setECResponseControl( respEntry, opContext, ChangeType.MODDN );
209            session.getIoSession().write( respEntry );
210        }
211    }