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 */ 020package org.apache.directory.server.ldap.handlers; 021 022 023import org.apache.directory.api.ldap.model.entry.Entry; 024import org.apache.directory.api.ldap.model.exception.LdapException; 025import org.apache.directory.api.ldap.model.message.AbandonListener; 026import org.apache.directory.api.ldap.model.message.AbandonableRequest; 027import org.apache.directory.api.ldap.model.message.SearchRequest; 028import org.apache.directory.api.ldap.model.message.SearchResultEntry; 029import org.apache.directory.api.ldap.model.message.SearchResultEntryImpl; 030import org.apache.directory.api.ldap.model.message.controls.ChangeType; 031import org.apache.directory.api.ldap.model.message.controls.EntryChange; 032import org.apache.directory.api.ldap.model.message.controls.EntryChangeImpl; 033import org.apache.directory.api.ldap.model.message.controls.PersistentSearch; 034import org.apache.directory.api.ldap.model.schema.SchemaManager; 035import org.apache.directory.api.util.Strings; 036import org.apache.directory.server.core.api.entry.ClonedServerEntry; 037import org.apache.directory.server.core.api.entry.ServerEntryUtils; 038import org.apache.directory.server.core.api.event.DirectoryListener; 039import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 040import org.apache.directory.server.core.api.interceptor.context.ChangeOperationContext; 041import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 042import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 043import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 044import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 045import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 046import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 047import org.apache.directory.server.ldap.LdapSession; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051 052/** 053 * A DirectoryListener implementation which sends back added, deleted, modified or 054 * renamed entries to a client that created this listener. This class is part of the 055 * persistent search implementation which uses the event notification scheme built into 056 * the server core. 057 * 058 * This listener is disabled only when a session closes or when an abandon request 059 * cancels it. Hence time and size limits in normal search operations do not apply 060 * here. 061 * 062 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 063 */ 064public class PersistentSearchListener implements DirectoryListener, AbandonListener 065{ 066 private static final Logger LOG = LoggerFactory.getLogger( PersistentSearchListener.class ); 067 final LdapSession session; 068 final SearchRequest req; 069 final PersistentSearch psearchControl; 070 071 private LookupOperationContext filterCtx; 072 private SchemaManager schemaManager; 073 074 public PersistentSearchListener( LdapSession session, SearchRequest req ) 075 { 076 this.session = session; 077 this.req = req; 078 req.addAbandonListener( this ); 079 this.psearchControl = ( PersistentSearch ) req.getControls().get( PersistentSearch.OID ); 080 081 filterCtx = new LookupOperationContext( session.getCoreSession(), req.getAttributes().toArray( Strings.EMPTY_STRING_ARRAY ) ); 082 schemaManager = session.getCoreSession().getDirectoryService().getSchemaManager(); 083 } 084 085 086 @Override 087 public boolean isSynchronous() 088 { 089 return false; // always asynchronous 090 } 091 092 093 public void abandon() 094 { 095 // must abandon the operation 096 session.getCoreSession().getDirectoryService().getEventService().removeListener( this ); 097 098 /* 099 * From RFC 2251 Section 4.11: 100 * 101 * In the event that a server receives an Abandon Request on a Search 102 * operation in the midst of transmitting responses to the Search, that 103 * server MUST cease transmitting entry responses to the abandoned 104 * request immediately, and MUST NOT send the SearchResultDone. Of 105 * course, the server MUST ensure that only properly encoded LDAPMessage 106 * PDUs are transmitted. 107 * 108 * SO DON'T SEND BACK ANYTHING!!!!! 109 */ 110 } 111 112 113 public void requestAbandoned( AbandonableRequest req ) 114 { 115 abandon(); 116 } 117 118 119 private void setECResponseControl( SearchResultEntry response, ChangeOperationContext opContext, ChangeType type ) 120 { 121 if ( psearchControl.isReturnECs() ) 122 { 123 EntryChange ecControl = new EntryChangeImpl(); 124 ecControl.setChangeType( type ); 125 126 if ( opContext.getChangeLogEvent() != null ) 127 { 128 ecControl.setChangeNumber( opContext.getChangeLogEvent().getRevision() ); 129 } 130 131 if ( opContext instanceof RenameOperationContext || opContext instanceof MoveOperationContext ) 132 { 133 ecControl.setPreviousDn( opContext.getDn() ); 134 } 135 136 response.addControl( ecControl ); 137 } 138 } 139 140 141 public void entryAdded( AddOperationContext addContext ) 142 { 143 if ( !psearchControl.isNotificationEnabled( ChangeType.ADD ) ) 144 { 145 return; 146 } 147 148 SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() ); 149 respEntry.setObjectName( addContext.getDn() ); 150 151 // the entry needs to be cloned cause addContext.getEntry() will only contain 152 // the user provided values and all the operational attributes added during 153 // Partition.add() will be applied in the cloned entry present inside it 154 // if we don't clone then the attributes will not be filtered 155 // e.x the operational attributes will also be sent even when a user requests 156 // user attributes only 157 Entry entry = new ClonedServerEntry( addContext.getEntry() ); 158 filterEntry( entry ); 159 respEntry.setEntry( entry ); 160 161 setECResponseControl( respEntry, addContext, ChangeType.ADD ); 162 session.getIoSession().write( respEntry ); 163 } 164 165 166 public void entryDeleted( DeleteOperationContext deleteContext ) 167 { 168 if ( !psearchControl.isNotificationEnabled( ChangeType.DELETE ) ) 169 { 170 return; 171 } 172 173 SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() ); 174 respEntry.setObjectName( deleteContext.getDn() ); 175 filterEntry( deleteContext.getEntry() ); 176 respEntry.setEntry( deleteContext.getEntry() ); 177 setECResponseControl( respEntry, deleteContext, ChangeType.DELETE ); 178 session.getIoSession().write( respEntry ); 179 } 180 181 182 public void entryModified( ModifyOperationContext modifyContext ) 183 { 184 if ( !psearchControl.isNotificationEnabled( ChangeType.MODIFY ) ) 185 { 186 return; 187 } 188 189 SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() ); 190 respEntry.setObjectName( modifyContext.getDn() ); 191 192 Entry entry = new ClonedServerEntry( modifyContext.getAlteredEntry() ); 193 filterEntry( entry ); 194 respEntry.setEntry( entry ); 195 196 setECResponseControl( respEntry, modifyContext, ChangeType.MODIFY ); 197 session.getIoSession().write( respEntry ); 198 } 199 200 201 public void entryMoved( MoveOperationContext moveContext ) 202 { 203 if ( !psearchControl.isNotificationEnabled( ChangeType.MODDN ) ) 204 { 205 return; 206 } 207 208 SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() ); 209 respEntry.setObjectName( moveContext.getNewDn() ); 210 211 Entry entry = new ClonedServerEntry( moveContext.getModifiedEntry() ); 212 filterEntry( entry ); 213 respEntry.setEntry( entry ); 214 215 setECResponseControl( respEntry, moveContext, ChangeType.MODDN ); 216 session.getIoSession().write( respEntry ); 217 } 218 219 220 public void entryMovedAndRenamed( MoveAndRenameOperationContext moveAndRenameContext ) 221 { 222 entryRenamed( moveAndRenameContext ); 223 } 224 225 226 public void entryRenamed( RenameOperationContext renameContext ) 227 { 228 if ( !psearchControl.isNotificationEnabled( ChangeType.MODDN ) ) 229 { 230 return; 231 } 232 233 SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() ); 234 respEntry.setObjectName( renameContext.getModifiedEntry().getDn() ); 235 236 Entry entry = new ClonedServerEntry( renameContext.getModifiedEntry() ); 237 filterEntry( entry ); 238 respEntry.setEntry( entry ); 239 240 setECResponseControl( respEntry, renameContext, ChangeType.MODDN ); 241 session.getIoSession().write( respEntry ); 242 } 243 244 245 /** 246 * A convenient method to filter the contents of an entry 247 * 248 * @see ServerEntryUtils#filterContents(SchemaManager, org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext, Entry) 249 * 250 * @param entry 251 */ 252 private void filterEntry( Entry entry ) 253 { 254 try 255 { 256 ServerEntryUtils.filterContents( schemaManager, filterCtx, entry ); 257 } 258 catch ( LdapException e ) 259 { 260 // shouldn't happen, if it does then blow up 261 throw new RuntimeException( e ); 262 } 263 } 264}