| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.ldap.handlers; |
| |
| |
| import javax.naming.InvalidNameException; |
| import javax.naming.NamingException; |
| import javax.naming.directory.Attributes; |
| import javax.naming.event.NamespaceChangeListener; |
| import javax.naming.event.NamingEvent; |
| import javax.naming.event.NamingExceptionEvent; |
| import javax.naming.event.ObjectChangeListener; |
| |
| import org.apache.directory.server.core.entry.ServerEntry; |
| import org.apache.directory.server.core.entry.ServerEntryUtils; |
| import org.apache.directory.server.core.jndi.ServerLdapContext; |
| import org.apache.directory.server.ldap.SessionRegistry; |
| import org.apache.directory.shared.ldap.codec.search.controls.ChangeType; |
| import org.apache.directory.shared.ldap.exception.LdapException; |
| import org.apache.directory.shared.ldap.exception.OperationAbandonedException; |
| import org.apache.directory.shared.ldap.message.AbandonListener; |
| import org.apache.directory.shared.ldap.message.AbandonableRequest; |
| import org.apache.directory.shared.ldap.message.EntryChangeControl; |
| import org.apache.directory.shared.ldap.message.LdapResult; |
| import org.apache.directory.shared.ldap.message.PersistentSearchControl; |
| import org.apache.directory.shared.ldap.message.ResultCodeEnum; |
| import org.apache.directory.shared.ldap.message.SearchRequest; |
| import org.apache.directory.shared.ldap.message.SearchResponseEntry; |
| import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl; |
| import org.apache.directory.shared.ldap.name.LdapDN; |
| import org.apache.directory.shared.ldap.util.ExceptionUtils; |
| import org.apache.mina.common.IoSession; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * A JNDI NamingListener implementation which sends back added, deleted, modified or |
| * renamed entries to a client that created this listener. This class is part of the |
| * persistent search implementation which uses the event notification scheme built into |
| * the server core. This is exposed by the server side ApacheDS JNDI LDAP provider. |
| * |
| * This listener is disabled only when a session closes or when an abandon request |
| * cancels it. Hence time and size limits in normal search operations do not apply |
| * here. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| * @version $Rev$ |
| */ |
| class PersistentSearchListener implements ObjectChangeListener, NamespaceChangeListener, AbandonListener |
| { |
| private static final Logger LOG = LoggerFactory.getLogger( SearchHandler.class ); |
| final ServerLdapContext ctx; |
| final IoSession session; |
| final SearchRequest req; |
| final PersistentSearchControl control; |
| final SessionRegistry registry; |
| |
| |
| /** Speedup for logs */ |
| private static final boolean IS_DEBUG = LOG.isDebugEnabled(); |
| |
| |
| PersistentSearchListener( SessionRegistry registry, ServerLdapContext ctx, IoSession session, SearchRequest req ) |
| { |
| this.registry = registry; |
| this.session = session; |
| this.req = req; |
| req.addAbandonListener( this ); |
| this.ctx = ctx; |
| this.control = ( PersistentSearchControl ) req.getControls().get( PersistentSearchControl.CONTROL_OID ); |
| } |
| |
| |
| public void abandon() throws NamingException |
| { |
| // must abandon the operation |
| ctx.removeNamingListener( this ); |
| |
| /* |
| * From RFC 2251 Section 4.11: |
| * |
| * In the event that a server receives an Abandon Request on a Search |
| * operation in the midst of transmitting responses to the Search, that |
| * server MUST cease transmitting entry responses to the abandoned |
| * request immediately, and MUST NOT send the SearchResultDone. Of |
| * course, the server MUST ensure that only properly encoded LDAPMessage |
| * PDUs are transmitted. |
| * |
| * SO DON'T SEND BACK ANYTHING!!!!! |
| */ |
| } |
| |
| |
| public void namingExceptionThrown( NamingExceptionEvent evt ) |
| { |
| // must abandon the operation and send response done with an |
| // error message if this occurs because something is wrong |
| |
| try |
| { |
| ctx.removeNamingListener( this ); |
| } |
| catch ( NamingException e ) |
| { |
| LOG.error( "Attempt to remove listener from context failed", e ); |
| } |
| |
| /* |
| * From RFC 2251 Section 4.11: |
| * |
| * In the event that a server receives an Abandon Request on a Search |
| * operation in the midst of transmitting responses to the Search, that |
| * server MUST cease transmitting entry responses to the abandoned |
| * request immediately, and MUST NOT send the SearchResultDone. Of |
| * course, the server MUST ensure that only properly encoded LDAPMessage |
| * PDUs are transmitted. |
| * |
| * SO DON'T SEND BACK ANYTHING!!!!! |
| */ |
| if ( evt.getException() instanceof OperationAbandonedException ) |
| { |
| return; |
| } |
| |
| registry.removeOutstandingRequest( session, new Integer( req.getMessageId() ) ); |
| String msg = "failed on persistent search operation"; |
| |
| if ( IS_DEBUG ) |
| { |
| msg += ":\n" + req + ":\n" + ExceptionUtils.getStackTrace( evt.getException() ); |
| } |
| |
| ResultCodeEnum code; |
| |
| if ( evt.getException() instanceof LdapException ) |
| { |
| code = ( ( LdapException ) evt.getException() ).getResultCode(); |
| } |
| else |
| { |
| code = ResultCodeEnum.getBestEstimate( evt.getException(), req.getType() ); |
| } |
| |
| LdapResult result = req.getResultResponse().getLdapResult(); |
| result.setResultCode( code ); |
| result.setErrorMessage( msg ); |
| |
| if ( ( evt.getException().getResolvedName() != null ) |
| && ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM ) |
| || ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) ) |
| { |
| result.setMatchedDn( (LdapDN)evt.getException().getResolvedName() ); |
| } |
| |
| session.write( req.getResultResponse() ); |
| } |
| |
| |
| public void objectChanged( NamingEvent evt ) |
| { |
| // send the entry back |
| sendEntry( evt ); |
| } |
| |
| |
| public void objectAdded( NamingEvent evt ) |
| { |
| // send the entry back |
| sendEntry( evt ); |
| } |
| |
| |
| public void objectRemoved( NamingEvent evt ) |
| { |
| // send the entry back |
| sendEntry( evt ); |
| } |
| |
| |
| public void objectRenamed( NamingEvent evt ) |
| { |
| // send the entry back |
| sendEntry( evt ); |
| } |
| |
| |
| private void sendEntry( NamingEvent evt ) |
| { |
| /* |
| * @todo eventually you'll want to add the changeNumber once we move |
| * the CSN functionality into the server. |
| */ |
| SearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() ); |
| EntryChangeControl ecControl = null; |
| |
| if ( control.isReturnECs() ) |
| { |
| ecControl = new EntryChangeControl(); |
| respEntry.add( ecControl ); |
| } |
| |
| LdapDN newBinding = null; |
| LdapDN oldBinding = null; |
| |
| if ( evt.getNewBinding() != null ) |
| { |
| try |
| { |
| newBinding = new LdapDN( evt.getNewBinding().getName() ); |
| } |
| catch ( InvalidNameException ine ) |
| { |
| newBinding = LdapDN.EMPTY_LDAPDN; |
| } |
| } |
| |
| if ( evt.getOldBinding() != null ) |
| { |
| try |
| { |
| oldBinding = new LdapDN( evt.getOldBinding().getName() ); |
| } |
| catch ( InvalidNameException ine ) |
| { |
| oldBinding = LdapDN.EMPTY_LDAPDN; |
| } |
| } |
| |
| Object attr; |
| |
| switch ( evt.getType() ) |
| { |
| case ( NamingEvent.OBJECT_ADDED ): |
| if ( !control.isNotificationEnabled( ChangeType.ADD ) ) |
| { |
| return; |
| } |
| |
| respEntry.setObjectName( newBinding ); |
| attr = evt.getChangeInfo(); |
| |
| if ( attr instanceof ServerEntry ) |
| { |
| respEntry.setAttributes( ServerEntryUtils.toAttributesImpl( (ServerEntry)attr ) ); |
| } |
| else |
| { |
| respEntry.setAttributes( ( Attributes ) attr ); |
| } |
| |
| if ( ecControl != null ) |
| { |
| ecControl.setChangeType( ChangeType.ADD ); |
| } |
| |
| break; |
| |
| case ( NamingEvent.OBJECT_CHANGED ): |
| if ( !control.isNotificationEnabled( ChangeType.MODIFY ) ) |
| { |
| return; |
| } |
| |
| respEntry.setObjectName( oldBinding ); |
| attr = evt.getOldBinding().getObject(); |
| |
| if ( attr instanceof ServerEntry ) |
| { |
| respEntry.setAttributes( ServerEntryUtils.toAttributesImpl( (ServerEntry)attr ) ); |
| } |
| else |
| { |
| respEntry.setAttributes( ( Attributes ) attr ); |
| } |
| |
| if ( ecControl != null ) |
| { |
| ecControl.setChangeType( ChangeType.MODIFY ); |
| } |
| |
| break; |
| |
| case ( NamingEvent.OBJECT_REMOVED ): |
| if ( !control.isNotificationEnabled( ChangeType.DELETE ) ) |
| { |
| return; |
| } |
| |
| respEntry.setObjectName( oldBinding ); |
| attr = evt.getOldBinding().getObject(); |
| |
| if ( attr instanceof ServerEntry ) |
| { |
| respEntry.setAttributes( ServerEntryUtils.toAttributesImpl( (ServerEntry)attr ) ); |
| } |
| else |
| { |
| respEntry.setAttributes( ( Attributes ) attr ); |
| } |
| |
| if ( ecControl != null ) |
| { |
| ecControl.setChangeType( ChangeType.DELETE ); |
| } |
| |
| break; |
| |
| case ( NamingEvent.OBJECT_RENAMED ): |
| if ( !control.isNotificationEnabled( ChangeType.MODDN ) ) |
| { |
| return; |
| } |
| |
| respEntry.setObjectName( newBinding ); |
| attr = evt.getNewBinding().getObject(); |
| |
| if ( attr instanceof ServerEntry ) |
| { |
| respEntry.setAttributes( ServerEntryUtils.toAttributesImpl( (ServerEntry)attr ) ); |
| } |
| else |
| { |
| respEntry.setAttributes( ( Attributes ) attr ); |
| } |
| |
| if ( ecControl != null ) |
| { |
| ecControl.setChangeType( ChangeType.MODDN ); |
| ecControl.setPreviousDn( oldBinding ); |
| } |
| |
| break; |
| |
| default: |
| return; |
| } |
| |
| session.write( respEntry ); |
| } |
| |
| |
| public void requestAbandoned( AbandonableRequest req ) |
| { |
| try |
| { |
| abandon(); |
| } |
| catch ( NamingException e ) |
| { |
| LOG.error( "failed to properly abandon this persistent search", e ); |
| } |
| } |
| } |