View Javadoc

1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.server.ldap.handlers;
21  
22  
23  import javax.naming.InvalidNameException;
24  import javax.naming.NameNotFoundException;
25  import javax.naming.NamingException;
26  
27  import org.apache.directory.server.core.entry.ClonedServerEntry;
28  import org.apache.directory.server.core.entry.ServerAttribute;
29  import org.apache.directory.server.ldap.LdapSession;
30  import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
31  import org.apache.directory.shared.ldap.constants.SchemaConstants;
32  import org.apache.directory.shared.ldap.entry.EntryAttribute;
33  import org.apache.directory.shared.ldap.entry.Value;
34  import org.apache.directory.shared.ldap.exception.LdapException;
35  import org.apache.directory.shared.ldap.message.AddRequest;
36  import org.apache.directory.shared.ldap.message.BindRequest;
37  import org.apache.directory.shared.ldap.message.CompareRequest;
38  import org.apache.directory.shared.ldap.message.DeleteRequest;
39  import org.apache.directory.shared.ldap.message.LdapResult;
40  import org.apache.directory.shared.ldap.message.ManageDsaITControl;
41  import org.apache.directory.shared.ldap.message.ModifyDnRequest;
42  import org.apache.directory.shared.ldap.message.ModifyRequest;
43  import org.apache.directory.shared.ldap.message.Referral;
44  import org.apache.directory.shared.ldap.message.ReferralImpl;
45  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
46  import org.apache.directory.shared.ldap.message.ResultResponseRequest;
47  import org.apache.directory.shared.ldap.message.SearchRequest;
48  import org.apache.directory.shared.ldap.name.LdapDN;
49  import org.apache.directory.shared.ldap.util.ExceptionUtils;
50  import org.apache.directory.shared.ldap.util.LdapURL;
51  
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * A based class for handlers which deal with SingleReplyRequests.  This class 
58   * provides various capabilities out of the box for these kinds of requests so
59   * common handling code is not duplicated.  Namely, exception handling and 
60   * referral handling code common to most SingleReplyRequests (minus 
61   * ExtendedRequests) are handled thanks to this class.
62   *
63   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64   * @version $Rev$, $Date$
65   */
66  public abstract class ReferralAwareRequestHandler<T extends ResultResponseRequest> extends LdapRequestHandler<T>
67  {
68      private static final Logger LOG = LoggerFactory.getLogger( ReferralAwareRequestHandler.class );
69      
70      /** Speedup for logs */
71      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
72  
73      
74      /* (non-Javadoc)
75       * @see org.apache.directory.server.ldap.handlers.LdapRequestHandler#handle(org.apache.directory.server.ldap.LdapSession, org.apache.directory.shared.ldap.message.Request)
76       */
77      @Override
78      public final void handle( LdapSession session, T req ) throws Exception
79      {
80          LOG.debug( "Handling single reply request: {}", req );
81          
82          LdapDN reqTargetDn = null;
83          
84          switch ( req.getType() )
85          {
86              case ADD_REQUEST:
87                  reqTargetDn = ( ( AddRequest ) req ).getEntryDn();
88                  break;
89              case BIND_REQUEST:
90                  // not used for bind but may be in future
91                  reqTargetDn = ( ( BindRequest ) req ).getName();
92                  break;
93              case COMPARE_REQUEST:
94                  reqTargetDn = ( ( CompareRequest ) req ).getName();
95                  break;
96              case DEL_REQUEST:
97                  reqTargetDn = ( ( DeleteRequest ) req ).getName();
98                  break;
99              case EXTENDED_REQ:
100                 throw new IllegalStateException( 
101                     "Although ExtendedRequests are SingleReplyRequests they're not handled" +
102                     " using this base class.  They have no target entry unlike the rest of" +
103                     " the SingleReplyRequests" );
104             case MOD_DN_REQUEST:
105                 /*
106                  * Special handling needed because of the new superior entry 
107                  * as specified in RFC 3296 section 5.6.2 here: 
108                  *    
109                  *     http://www.faqs.org/rfcs/rfc3296.html
110                  */
111                 if ( req.getControls().containsKey( ManageDsaITControl.CONTROL_OID ) )
112                 {
113                     LOG.debug( "ManageDsaITControl detected." );
114                     handleIgnoringReferrals( session, ( ( ModifyDnRequest ) req ).getName(), null, req );
115                 }
116                 else
117                 {
118                     LOG.debug( "ManageDsaITControl NOT detected." );
119                     
120                     if ( ( ( ModifyDnRequest ) req ).getNewSuperior() == null )
121                     {
122                         handleWithReferrals( session, ( ( ModifyDnRequest ) req ).getName(), req );
123                     }
124                     else
125                     {
126                         // NOTE: call is to overload just for the ModifyDnRequest
127                         handleModifyDnWithReferrals( session, req );
128                     }
129                 }
130                 return;
131             case MODIFY_REQUEST:
132                 reqTargetDn = ( ( ModifyRequest ) req ).getName();
133                 break;
134             case SEARCH_REQUEST:
135                 reqTargetDn = ( ( SearchRequest ) req ).getBase();
136                 break;
137             default:
138                 throw new IllegalStateException( 
139                     "Unidentified single reply request/response type: " + req );
140         }
141         
142         if ( req.getControls().containsKey( ManageDsaITControl.CONTROL_OID ) )
143         {
144             LOG.debug( "ManageDsaITControl detected." );
145             handleIgnoringReferrals( session, reqTargetDn, null, req );
146         }
147         else
148         {
149             LOG.debug( "ManageDsaITControl NOT detected." );
150             handleWithReferrals( session, reqTargetDn, req );
151         }
152     }
153 
154     
155     public static final boolean isEntryReferral( ClonedServerEntry entry ) throws Exception
156     {
157         return entry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.REFERRAL_OC );
158     }
159     
160     
161     /**
162      * Searches up the ancestry of a DN searching for the farthest referral 
163      * ancestor.  This is required to properly handle referrals.  Note that 
164      * this function is quite costly since it attempts to lookup all the 
165      * ancestors up the hierarchy just to see if they represent referrals. 
166      * Techniques can be employed later to improve this performance hit by
167      * having an intelligent referral cache.
168      *
169      * @return the farthest referral ancestor or null
170      * @throws Exception if there are problems during this search
171      */
172     public static final ClonedServerEntry getFarthestReferralAncestor( LdapSession session, LdapDN target ) 
173         throws Exception
174     {
175         ClonedServerEntry entry;
176         ClonedServerEntry farthestReferralAncestor = null;
177         LdapDN dn = ( LdapDN ) target.clone();
178         
179         try
180         {
181             dn.remove( dn.size() - 1 );
182         }
183         catch ( InvalidNameException e2 )
184         {
185             // never thrown
186         }
187         
188         while ( ! dn.isEmpty() )
189         {
190             LOG.debug( "Walking ancestors of {} to find referrals.", dn );
191             
192             try
193             {
194                 entry = session.getCoreSession().lookup( dn );
195 
196                 if ( isEntryReferral( entry ) )
197                 {
198                     farthestReferralAncestor = entry;
199                 }
200 
201                 dn.remove( dn.size() - 1 );
202             }
203             catch ( NameNotFoundException e )
204             {
205                 LOG.debug( "Entry for {} not found.", dn );
206 
207                 // update the DN as we strip last component 
208                 try
209                 {
210                     dn.remove( dn.size() - 1 );
211                 }
212                 catch ( InvalidNameException e1 )
213                 {
214                     // never happens
215                 }
216             }
217         }
218         
219         return farthestReferralAncestor;
220     }
221     
222     
223     private void handleModifyDnWithReferrals( LdapSession session, T modifyDnRequest )
224     {
225         ModifyDnRequest req = ( ModifyDnRequest ) modifyDnRequest;
226         LdapResult result = req.getResultResponse().getLdapResult();
227         ClonedServerEntry entry = null;
228         ClonedServerEntry superiorEntry = null;
229 
230         // -------------------------------------------------------------------
231         // Lookup Entry
232         // -------------------------------------------------------------------
233         
234         // try to lookup the entry but ignore exceptions when it does not   
235         // exist since entry may not exist but may have an ancestor that is a 
236         // referral - would rather attempt a lookup that fails then do check 
237         // for existence than have to do another lookup to get entry info
238 
239         try
240         {
241             entry = session.getCoreSession().lookup( req.getName() );
242             LOG.debug( "Entry for {} was found: ", req.getName(), entry );
243         }
244         catch ( NameNotFoundException e )
245         {
246             /* ignore */
247             LOG.debug( "Entry for {} not found.", req.getName() );
248         }
249         catch ( Exception e )
250         {
251             /* serious and needs handling */
252             handleException( session, modifyDnRequest, e );
253             return;
254         }
255         
256         try
257         {
258             superiorEntry = session.getCoreSession().lookup( req.getNewSuperior() );
259             LOG.debug( "New superior entry for {} was found: ", req.getName(), entry );
260         }
261         catch ( NameNotFoundException e )
262         {
263             /* ignore */
264             LOG.debug( "New superior entry for {} not found.", req.getName() );
265         }
266         catch ( Exception e )
267         {
268             /* serious and needs handling */
269             handleException( session, modifyDnRequest, e );
270             return;
271         }
272         
273         // -------------------------------------------------------------------
274         // Handle Existing Entry
275         // -------------------------------------------------------------------
276         
277         if ( entry != null )
278         {
279             try
280             {
281                 if ( isEntryReferral( entry ) )
282                 {
283                     LOG.debug( "Entry is a referral: {}", entry );
284                     handleReferralEntry( session, req.getName(), modifyDnRequest, entry );
285                     return;
286                 }
287                 else
288                 {
289                     if ( superiorEntry != null && isEntryReferral( superiorEntry ) )
290                     {
291                         result.setErrorMessage( "Superior entry is a referral." );
292                         result.setMatchedDn( req.getNewSuperior() );
293                         result.setResultCode( ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
294                         session.getIoSession().write( req.getResultResponse() );
295                         return;
296                     }
297                     else if ( superiorEntry == null )
298                     {
299                         ClonedServerEntry referralAncestor = getFarthestReferralAncestor( session, 
300                             req.getNewSuperior() );
301                         
302                         if ( referralAncestor != null )
303                         {
304                             result.setErrorMessage( "Superior entry does has referral ancestor." );
305                             result.setResultCode( ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
306                             session.getIoSession().write( req.getResultResponse() );
307                             return;
308                         }
309                         else
310                         {
311                             result.setErrorMessage( "Superior entry does not exist." );
312                             result.setResultCode( ResultCodeEnum.NO_SUCH_OBJECT );
313                             session.getIoSession().write( req.getResultResponse() );
314                             return;
315                         }
316                     }
317                     
318                     LOG.debug( "Entry is NOT a referral: {}", entry );
319                     handleIgnoringReferrals( session, req.getName(), entry, modifyDnRequest );
320                     return;
321                 }
322             }
323             catch ( Exception e )
324             {
325                 handleException( session, modifyDnRequest, e );
326             }
327         }
328 
329         // -------------------------------------------------------------------
330         // Handle Non-existing Entry
331         // -------------------------------------------------------------------
332         
333         // if the entry is null we still have to check for a referral ancestor
334         // also the referrals need to be adjusted based on the ancestor's ref
335         // values to yield the correct path to the entry in the target DSAs
336         
337         if ( entry == null )
338         {
339             ClonedServerEntry referralAncestor = null;
340 
341             try
342             {
343                 referralAncestor = getFarthestReferralAncestor( session, req.getName() );
344             }
345             catch ( Exception e )
346             {
347                 handleException( session, modifyDnRequest, e );
348                 return;
349             }
350 
351             if ( referralAncestor == null && ! ( req instanceof AddRequest ) )
352             {
353                 result.setErrorMessage( "Entry not found." );
354                 result.setResultCode( ResultCodeEnum.NO_SUCH_OBJECT );
355                 session.getIoSession().write( req.getResultResponse() );
356                 return;
357             }
358             else if ( ( req instanceof AddRequest ) && referralAncestor == null )
359             {
360                 handleIgnoringReferrals( session, req.getName(), entry, modifyDnRequest );
361                 return;
362             }
363               
364             // if we get here then we have a valid referral ancestor
365             try
366             {
367                 Referral referral = getReferralOnAncestor( session, req.getName(), modifyDnRequest, referralAncestor );
368                 result.setResultCode( ResultCodeEnum.REFERRAL );
369                 result.setReferral( referral );
370                 session.getIoSession().write( req.getResultResponse() );
371             }
372             catch ( Exception e )
373             {
374                 handleException( session, modifyDnRequest, e );
375             }
376         }
377     }
378     
379     
380     /**
381      * Handles processing with referrals without ManageDsaIT control.
382      */
383     private void handleWithReferrals( LdapSession session, LdapDN reqTargetDn, T req )
384     {
385         LdapResult result = req.getResultResponse().getLdapResult();
386         ClonedServerEntry entry = null;
387 
388         // -------------------------------------------------------------------
389         // Lookup Entry
390         // -------------------------------------------------------------------
391         
392         // try to lookup the entry but ignore exceptions when it does not   
393         // exist since entry may not exist but may have an ancestor that is a 
394         // referral - would rather attempt a lookup that fails then do check 
395         // for existence than have to do another lookup to get entry info
396 
397         if ( ! ( req instanceof AddRequest ) )
398         {
399             try
400             {
401                 entry = session.getCoreSession().lookup( reqTargetDn );
402                 LOG.debug( "Entry for {} was found: ", reqTargetDn, entry );
403             }
404             catch ( NameNotFoundException e )
405             {
406                 /* ignore */
407                 LOG.debug( "Entry for {} not found.", reqTargetDn );
408             }
409             catch ( Exception e )
410             {
411                 /* serious and needs handling */
412                 handleException( session, req, e );
413                 return;
414             }
415         }
416         
417         // -------------------------------------------------------------------
418         // Handle Existing Entry
419         // -------------------------------------------------------------------
420         
421         if ( entry != null )
422         {
423             try
424             {
425                 if ( isEntryReferral( entry ) )
426                 {
427                     LOG.debug( "Entry is a referral: {}", entry );
428                     
429                     if ( req instanceof SearchRequest )
430                     {
431                         handleReferralEntryForSearch( session, ( SearchRequest ) req, entry );
432                     }
433                     else
434                     {
435                         handleReferralEntry( session, reqTargetDn, req, entry );
436                     }
437                     return;
438                 }
439                 else
440                 {
441                     LOG.debug( "Entry is NOT a referral: {}", entry );
442                     handleIgnoringReferrals( session, reqTargetDn, entry, req );
443                     return;
444                 }
445             }
446             catch ( Exception e )
447             {
448                 handleException( session, req, e );
449             }
450         }
451 
452         // -------------------------------------------------------------------
453         // Handle Non-existing Entry
454         // -------------------------------------------------------------------
455         
456         // if the entry is null we still have to check for a referral ancestor
457         // also the referrals need to be adjusted based on the ancestor's ref
458         // values to yield the correct path to the entry in the target DSAs
459         
460         if ( entry == null )
461         {
462             ClonedServerEntry referralAncestor = null;
463 
464             try
465             {
466                 referralAncestor = getFarthestReferralAncestor( session, reqTargetDn );
467             }
468             catch ( Exception e )
469             {
470                 handleException( session, req, e );
471                 return;
472             }
473 
474             if ( referralAncestor == null && ! ( req instanceof AddRequest ) )
475             {
476                 result.setErrorMessage( "Entry not found." );
477                 result.setResultCode( ResultCodeEnum.NO_SUCH_OBJECT );
478                 session.getIoSession().write( req.getResultResponse() );
479                 return;
480             }
481             else if ( ( req instanceof AddRequest ) && referralAncestor == null )
482             {
483                 handleIgnoringReferrals( session, reqTargetDn, entry, req );
484                 return;
485             }
486               
487             // if we get here then we have a valid referral ancestor
488             try
489             {
490                 Referral referral = null;
491                 
492                 if ( req instanceof SearchRequest )
493                 {
494                     referral = getReferralOnAncestorForSearch( session, ( SearchRequest ) req, referralAncestor );
495                 }
496                 else
497                 {
498                     referral = getReferralOnAncestor( session, reqTargetDn, req, referralAncestor );
499                 }
500                 
501                 result.setResultCode( ResultCodeEnum.REFERRAL );
502                 result.setReferral( referral );
503                 session.getIoSession().write( req.getResultResponse() );
504             }
505             catch ( Exception e )
506             {
507                 handleException( session, req, e );
508             }
509         }
510     }
511 
512     
513     /**
514      * Handles processing a referral response on a target entry which is a 
515      * referral.  It will for any request that returns an LdapResult in it's 
516      * response.
517      *
518      * @param session the session to use for processing
519      * @param reqTargetDn the dn of the target entry of the request
520      * @param req the request
521      * @param entry the entry associated with the request
522      */
523     private void handleReferralEntry( LdapSession session, LdapDN reqTargetDn, T req, ClonedServerEntry entry )
524     {
525         LdapResult result = req.getResultResponse().getLdapResult();
526         ReferralImpl refs = new ReferralImpl();
527         result.setReferral( refs );
528         result.setResultCode( ResultCodeEnum.REFERRAL );
529         result.setErrorMessage( "Encountered referral attempting to handle request." );
530         result.setMatchedDn( reqTargetDn );
531 
532         EntryAttribute refAttr = entry.getOriginalEntry().get( SchemaConstants.REF_AT );
533         for ( Value<?> refval : refAttr )
534         {
535             refs.addLdapUrl( ( String ) refval.get() );
536         }
537 
538         session.getIoSession().write( req.getResultResponse() );
539     }
540     
541     
542     /**
543      * Handles processing a referral response on a target entry which is a 
544      * referral.  It will for any request that returns an LdapResult in it's 
545      * response.
546      *
547      * @param session the session to use for processing
548      * @param reqTargetDn the dn of the target entry of the request
549      * @param req the request
550      * @param entry the entry associated with the request
551      */
552     private void handleReferralEntryForSearch( LdapSession session, SearchRequest req, ClonedServerEntry entry )
553         throws Exception
554     {
555         LdapResult result = req.getResultResponse().getLdapResult();
556         ReferralImpl referral = new ReferralImpl();
557         result.setReferral( referral );
558         result.setResultCode( ResultCodeEnum.REFERRAL );
559         result.setErrorMessage( "Encountered referral attempting to handle request." );
560         result.setMatchedDn( req.getBase() );
561 
562         EntryAttribute refAttr = entry.getOriginalEntry().get( SchemaConstants.REF_AT );
563         for ( Value<?> refval : refAttr )
564         {
565             String refstr = ( String ) refval.get();
566             
567             // need to add non-ldap URLs as-is
568             if ( ! refstr.startsWith( "ldap" ) )
569             {
570                 referral.addLdapUrl( refstr );
571                 continue;
572             }
573             
574             // parse the ref value and normalize the DN  
575             LdapURL ldapUrl = new LdapURL();
576             try
577             {
578                 ldapUrl.parse( refstr.toCharArray() );
579             }
580             catch ( LdapURLEncodingException e )
581             {
582                 LOG.error( "Bad URL ({}) for ref in {}.  Reference will be ignored.", refstr, entry );
583                 continue;
584             }
585             
586             ldapUrl.setForceScopeRendering( true );
587             ldapUrl.setAttributes( req.getAttributes() );
588             ldapUrl.setScope( req.getScope().getJndiScope() );
589             referral.addLdapUrl( ldapUrl.toString() );
590         }
591 
592         session.getIoSession().write( req.getResultResponse() );
593     }
594     
595     
596     /**
597      * Handles processing with referrals without ManageDsaIT control and with 
598      * an ancestor that is a referral.  The original entry was not found and 
599      * the walk of the ancestry returned a referral.
600      * 
601      * @param referralAncestor the farthest referral ancestor of the missing 
602      * entry  
603      */
604     public Referral getReferralOnAncestor( LdapSession session, LdapDN reqTargetDn, T req, 
605         ClonedServerEntry referralAncestor ) throws Exception
606     {
607         LOG.debug( "Inside getReferralOnAncestor()" );
608         
609         ServerAttribute refAttr = ( ServerAttribute ) referralAncestor.getOriginalEntry()
610             .get( SchemaConstants.REF_AT );
611         Referral referral = new ReferralImpl();
612 
613         for ( Value<?> value : refAttr )
614         {
615             String ref = ( String ) value.get();
616 
617             LOG.debug( "Calculating LdapURL for referrence value {}", ref );
618 
619             // need to add non-ldap URLs as-is
620             if ( ! ref.startsWith( "ldap" ) )
621             {
622                 referral.addLdapUrl( ref );
623                 continue;
624             }
625             
626             // parse the ref value and normalize the DN  
627             LdapURL ldapUrl = new LdapURL();
628             try
629             {
630                 ldapUrl.parse( ref.toCharArray() );
631             }
632             catch ( LdapURLEncodingException e )
633             {
634                 LOG.error( "Bad URL ({}) for ref in {}.  Reference will be ignored.", ref, referralAncestor );
635             }
636             
637             LdapDN urlDn = new LdapDN( ldapUrl.getDn().getUpName() );
638             urlDn.normalize( session.getCoreSession().getDirectoryService().getRegistries()
639                 .getAttributeTypeRegistry().getNormalizerMapping() ); 
640             
641             if ( urlDn.getNormName().equals( referralAncestor.getDn().getNormName() ) )
642             {
643                 // according to the protocol there is no need for the dn since it is the same as this request
644                 StringBuilder buf = new StringBuilder();
645                 buf.append( ldapUrl.getScheme() );
646                 buf.append( ldapUrl.getHost() );
647 
648                 if ( ldapUrl.getPort() > 0 )
649                 {
650                     buf.append( ":" );
651                     buf.append( ldapUrl.getPort() );
652                 }
653 
654                 referral.addLdapUrl( buf.toString() );
655                 continue;
656             }
657             
658             /*
659              * If we get here then the DN of the referral was not the same as the 
660              * DN of the ref LDAP URL.  We must calculate the remaining (difference)
661              * name past the farthest referral DN which the target name extends.
662              */
663             int diff = reqTargetDn.size() - referralAncestor.getDn().size();
664             LdapDN extra = new LdapDN();
665 
666             // TODO - fix this by access unormalized RDN values
667             // seems we have to do this because get returns normalized rdns
668             LdapDN reqUnnormalizedDn = new LdapDN( reqTargetDn.getUpName() );
669             for ( int jj = 0; jj < diff; jj++ )
670             {
671                 extra.add( reqUnnormalizedDn.get( referralAncestor.getDn().size() + jj ) );
672             }
673 
674             urlDn.addAll( extra );
675 
676             StringBuilder buf = new StringBuilder();
677             buf.append( ldapUrl.getScheme() );
678             buf.append( ldapUrl.getHost() );
679 
680             if ( ldapUrl.getPort() > 0 )
681             {
682                 buf.append( ":" );
683                 buf.append( ldapUrl.getPort() );
684             }
685 
686             buf.append( "/" );
687             buf.append( LdapURL.urlEncode( urlDn.getUpName(), false ) );
688             referral.addLdapUrl( buf.toString() );
689         }
690         
691         return referral;
692     }
693     
694     
695     /**
696      * Handles processing with referrals without ManageDsaIT control and with 
697      * an ancestor that is a referral.  The original entry was not found and 
698      * the walk of the ancestry returned a referral.
699      * 
700      * @param referralAncestor the farthest referral ancestor of the missing 
701      * entry  
702      */
703     public Referral getReferralOnAncestorForSearch( LdapSession session, SearchRequest req, 
704         ClonedServerEntry referralAncestor ) throws Exception
705     {
706         LOG.debug( "Inside getReferralOnAncestor()" );
707      
708         ServerAttribute refAttr = ( ServerAttribute ) referralAncestor.getOriginalEntry()
709             .get( SchemaConstants.REF_AT );
710         Referral referral = new ReferralImpl();
711 
712         for ( Value<?> value : refAttr )
713         {
714             String ref = ( String ) value.get();
715 
716             LOG.debug( "Calculating LdapURL for referrence value {}", ref );
717 
718             // need to add non-ldap URLs as-is
719             if ( ! ref.startsWith( "ldap" ) )
720             {
721                 referral.addLdapUrl( ref );
722                 continue;
723             }
724             
725             // Parse the ref value   
726             LdapURL ldapUrl = new LdapURL();
727             try
728             {
729                 ldapUrl.parse( ref.toCharArray() );
730             }
731             catch ( LdapURLEncodingException e )
732             {
733                 LOG.error( "Bad URL ({}) for ref in {}.  Reference will be ignored.", ref, referralAncestor );
734             }
735             
736             // Normalize the DN to check for same dn
737             LdapDN urlDn = new LdapDN( ldapUrl.getDn().getUpName() );
738             urlDn.normalize( session.getCoreSession().getDirectoryService().getRegistries()
739                 .getAttributeTypeRegistry().getNormalizerMapping() ); 
740             
741             if ( urlDn.getNormName().equals( req.getBase().getNormName() ) )
742             {
743                 ldapUrl.setForceScopeRendering( true );
744                 ldapUrl.setAttributes( req.getAttributes() );
745                 ldapUrl.setScope( req.getScope().getJndiScope() );
746                 referral.addLdapUrl( ldapUrl.toString() );
747                 continue;
748             }
749             
750             /*
751              * If we get here then the DN of the referral was not the same as the 
752              * DN of the ref LDAP URL.  We must calculate the remaining (difference)
753              * name past the farthest referral DN which the target name extends.
754              */
755             int diff = req.getBase().size() - referralAncestor.getDn().size();
756             LdapDN extra = new LdapDN();
757 
758             // TODO - fix this by access unormalized RDN values
759             // seems we have to do this because get returns normalized rdns
760             LdapDN reqUnnormalizedDn = new LdapDN( req.getBase().getUpName() );
761             for ( int jj = 0; jj < diff; jj++ )
762             {
763                 extra.add( reqUnnormalizedDn.get( referralAncestor.getDn().size() + jj ) );
764             }
765 
766             ldapUrl.getDn().addAll( extra );
767             ldapUrl.setForceScopeRendering( true );
768             ldapUrl.setAttributes( req.getAttributes() );
769             ldapUrl.setScope( req.getScope().getJndiScope() );
770             referral.addLdapUrl( ldapUrl.toString() );
771         }
772         
773         return referral;
774     }
775     
776     
777     /**
778      * Handles processing with referrals without ManageDsaIT control.
779      */
780     public void handleException( LdapSession session, T req, Exception e )
781     {
782         LdapResult result = req.getResultResponse().getLdapResult();
783 
784         /*
785          * Set the result code or guess the best option.
786          */
787         ResultCodeEnum code;
788         if ( e instanceof LdapException )
789         {
790             code = ( ( LdapException ) e ).getResultCode();
791         }
792         else
793         {
794             code = ResultCodeEnum.getBestEstimate( e, req.getType() );
795         }
796         result.setResultCode( code );
797 
798         /*
799          * Setup the error message to put into the request and put entire
800          * exception into the message if we are in debug mode.  Note we 
801          * embed the result code name into the message.
802          */
803         String msg = code.toString() + ": failed for " + req + ": " + e.getMessage();
804         LOG.error( msg, e );
805         if ( IS_DEBUG )
806         {
807             msg += ":\n" + ExceptionUtils.getStackTrace( e );
808         }
809         result.setErrorMessage( msg );
810 
811         if ( e instanceof NamingException )
812         {
813             NamingException ne = ( NamingException ) e;
814 
815             // Add the matchedDN if necessary
816             boolean setMatchedDn = 
817                 code == ResultCodeEnum.NO_SUCH_OBJECT             || 
818                 code == ResultCodeEnum.ALIAS_PROBLEM              ||
819                 code == ResultCodeEnum.INVALID_DN_SYNTAX          || 
820                 code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
821             
822             if ( ( ne.getResolvedName() != null ) && setMatchedDn )
823             {
824                 result.setMatchedDn( ( LdapDN ) ne.getResolvedName() );
825             }
826         }
827 
828         session.getIoSession().write( req.getResultResponse() );
829     }
830 
831     
832     /**
833      * Handles processing without referral handling in effect: either with the
834      * ManageDsaIT control or when the entry or all of it's ancestors are non-
835      * referral entries.
836      * 
837      * Implementors
838      * 
839      * @param session the LDAP session under which processing occurs
840      * @param reqTargetDn the target entry DN associated with the request
841      * @param entry the target entry if it exists and has been looked up, may 
842      * be null even if the entry exists, offered in case the entry is looked
843      * up to avoid repeat lookups.  Implementations should check if the entry
844      * is null and attempt a lookup instead of presuming the entry does not 
845      * exist.
846      * @param req the request to be handled
847      */
848     public abstract void handleIgnoringReferrals( LdapSession session, LdapDN reqTargetDn, 
849         ClonedServerEntry entry, T req );
850 }