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.ldap.LdapSession;
034 import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
035 import org.apache.directory.shared.ldap.message.AbandonListener;
036 import org.apache.directory.shared.ldap.message.InternalAbandonableRequest;
037 import org.apache.directory.shared.ldap.message.InternalSearchRequest;
038 import org.apache.directory.shared.ldap.message.InternalSearchResponseEntry;
039 import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl;
040 import org.apache.directory.shared.ldap.message.control.EntryChangeControl;
041 import org.apache.directory.shared.ldap.message.control.PersistentSearchControl;
042
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: 764131 $
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( "failed to properly abandon this persistent search", 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.getEntry() );
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 }