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.core.jndi;
21  
22  
23  import org.apache.directory.server.core.CoreSession;
24  import org.apache.directory.server.core.DefaultCoreSession;
25  import org.apache.directory.server.core.DirectoryService;
26  import org.apache.directory.server.core.authn.LdapPrincipal;
27  import org.apache.directory.shared.ldap.entry.EntryAttribute;
28  import org.apache.directory.server.core.entry.ClonedServerEntry;
29  import org.apache.directory.server.core.entry.ServerEntry;
30  import org.apache.directory.server.core.entry.ServerEntryUtils;
31  import org.apache.directory.server.core.event.DirectoryListener;
32  import org.apache.directory.server.core.event.NotificationCriteria;
33  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
34  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
35  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
36  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
37  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
38  import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
39  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
40  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
41  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
42  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
43  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
44  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
45  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
46  import org.apache.directory.shared.ldap.constants.JndiPropertyConstants;
47  import org.apache.directory.shared.ldap.constants.SchemaConstants;
48  import org.apache.directory.shared.ldap.entry.Modification;
49  import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
50  import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
51  import org.apache.directory.shared.ldap.filter.ExprNode;
52  import org.apache.directory.shared.ldap.filter.PresenceNode;
53  import org.apache.directory.shared.ldap.filter.SearchScope;
54  import org.apache.directory.shared.ldap.message.AliasDerefMode;
55  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
56  import org.apache.directory.shared.ldap.name.AttributeTypeAndValue;
57  import org.apache.directory.shared.ldap.name.LdapDN;
58  import org.apache.directory.shared.ldap.name.Rdn;
59  import org.apache.directory.shared.ldap.util.AttributeUtils;
60  import org.apache.directory.shared.ldap.util.StringTools;
61  
62  import javax.naming.Context;
63  import javax.naming.InvalidNameException;
64  import javax.naming.Name;
65  import javax.naming.NameNotFoundException;
66  import javax.naming.NameParser;
67  import javax.naming.NamingEnumeration;
68  import javax.naming.NamingException;
69  import javax.naming.Reference;
70  import javax.naming.Referenceable;
71  import javax.naming.directory.DirContext;
72  import javax.naming.directory.SearchControls;
73  import javax.naming.event.EventContext;
74  import javax.naming.event.NamingListener;
75  import javax.naming.ldap.Control;
76  import javax.naming.spi.DirStateFactory;
77  import javax.naming.spi.DirectoryManager;
78  import java.io.Serializable;
79  import java.util.HashMap;
80  import java.util.Hashtable;
81  import java.util.List;
82  import java.util.Map;
83  
84  
85  /**
86   * A non-federated abstract Context implementation.
87   *
88   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
89   * @version $Rev: 691168 $
90   */
91  public abstract class ServerContext implements EventContext
92  {
93      /** property key used for deleting the old RDN on a rename */
94      public static final String DELETE_OLD_RDN_PROP = JndiPropertyConstants.JNDI_LDAP_DELETE_RDN;
95  
96      /** Empty array of controls for use in dealing with them */
97      protected static final Control[] EMPTY_CONTROLS = new Control[0];
98  
99      /** The directory service which owns this context **/
100     private final DirectoryService service;
101 
102     /** The cloned environment used by this Context */
103     private final Hashtable<String, Object> env;
104 
105     /** The distinguished name of this Context */
106     private final LdapDN dn;
107 
108     /** The set of registered NamingListeners */
109     private final Map<NamingListener,DirectoryListener> listeners = 
110         new HashMap<NamingListener,DirectoryListener>();
111 
112     /** The request controls to set on operations before performing them */
113     protected Control[] requestControls = EMPTY_CONTROLS;
114 
115     /** The response controls to set after performing operations */
116     protected Control[] responseControls = EMPTY_CONTROLS;
117 
118     /** Connection level controls associated with the session */
119     protected Control[] connectControls = EMPTY_CONTROLS;
120     
121     private final CoreSession session;
122 
123 
124     // ------------------------------------------------------------------------
125     // Constructors
126     // ------------------------------------------------------------------------
127 
128     
129     /**
130      * Must be called by all subclasses to initialize the nexus proxy and the
131      * environment settings to be used by this Context implementation.  This
132      * specific contstructor relies on the presence of the {@link
133      * Context#PROVIDER_URL} key and value to determine the distinguished name
134      * of the newly created context.  It also checks to make sure the
135      * referenced name actually exists within the system.  This constructor
136      * is used for all InitialContext requests.
137      * 
138      * @param service the parent service that manages this context
139      * @param env the environment properties used by this context.
140      * @throws NamingException if the environment parameters are not set 
141      * correctly.
142      */
143     @SuppressWarnings(value = { "unchecked" })
144     protected ServerContext( DirectoryService service, Hashtable<String, Object> env ) throws Exception
145     {
146         this.service = service;
147 
148         this.env = env;
149         
150         LdapJndiProperties props = LdapJndiProperties.getLdapJndiProperties( this.env );
151         dn = props.getProviderDn();
152 
153         /*
154          * Need do bind operation here, and opContext returned contains the 
155          * newly created session.
156          */
157         BindOperationContext opContext = doBindOperation( props.getBindDn(), props.getCredentials(), 
158             props.getSaslMechanism(), props.getSaslAuthId() );
159 
160         session = opContext.getSession();
161         
162         if ( ! service.getOperationManager().hasEntry( new EntryOperationContext( session, dn ) ) )
163         {
164             throw new NameNotFoundException( dn + " does not exist" );
165         }
166     }
167     
168     
169     /**
170      * Must be called by all subclasses to initialize the nexus proxy and the
171      * environment settings to be used by this Context implementation.  This
172      * constructor is used to propagate new contexts from existing contexts.
173      *
174      * @param principal the directory user principal that is propagated
175      * @param dn the distinguished name of this context
176      * @param service the directory service core
177      * @throws NamingException if there is a problem creating the new context
178      */
179     public ServerContext( DirectoryService service, LdapPrincipal principal, Name dn ) throws Exception
180     {
181         this.service = service;
182         this.dn = ( LdapDN ) dn.clone();
183 
184         this.env = new Hashtable<String, Object>();
185         this.env.put( PROVIDER_URL, dn.toString() );
186         this.env.put( DirectoryService.JNDI_KEY, service );
187         session = new DefaultCoreSession( principal, service );
188         
189         if ( ! service.getOperationManager().hasEntry( new EntryOperationContext( session, ( LdapDN ) dn ) ) )
190         {
191             throw new NameNotFoundException( dn + " does not exist" );
192         }
193     }
194 
195 
196     public ServerContext( DirectoryService service, CoreSession session, Name dn ) throws Exception
197     {
198         this.service = service;
199         this.dn = ( LdapDN ) dn.clone();
200         this.env = new Hashtable<String, Object>();
201         this.env.put( PROVIDER_URL, dn.toString() );
202         this.env.put( DirectoryService.JNDI_KEY, service );
203         this.session = session;
204         
205         if ( ! service.getOperationManager().hasEntry( new EntryOperationContext( session, ( LdapDN ) dn ) ) )
206         {
207             throw new NameNotFoundException( dn + " does not exist" );
208         }
209     }
210 
211 
212     // ------------------------------------------------------------------------
213     // Protected Methods for Operations
214     // ------------------------------------------------------------------------
215     // Use these methods instead of manually calling the nexusProxy so we can
216     // add request controls to operation contexts before the call and extract 
217     // response controls from the contexts after the call.  NOTE that the 
218     // requestControls must be cleared after each operation.  This makes a 
219     // context not thread safe.
220     // ------------------------------------------------------------------------
221 
222     /**
223      * Used to encapsulate [de]marshalling of controls before and after add operations.
224      * @param entry
225      * @param target
226      */
227     protected void doAddOperation( LdapDN target, ServerEntry entry ) throws Exception
228     {
229         // setup the op context and populate with request controls
230         AddOperationContext opCtx = new AddOperationContext( session, entry );
231 
232         opCtx.addRequestControls( requestControls );
233 
234         // execute add operation
235         service.getOperationManager().add( opCtx );
236 
237         // clear the request controls and set the response controls 
238         requestControls = EMPTY_CONTROLS;
239         responseControls = opCtx.getResponseControls();
240     }
241 
242 
243     /**
244      * Used to encapsulate [de]marshalling of controls before and after delete operations.
245      * @param target
246      */
247     protected void doDeleteOperation( LdapDN target ) throws Exception
248     {
249         // setup the op context and populate with request controls
250         DeleteOperationContext opCtx = new DeleteOperationContext( session, target );
251         opCtx.addRequestControls( requestControls );
252 
253         // execute delete operation
254         service.getOperationManager().delete( opCtx );
255 
256         // clear the request controls and set the response controls 
257         requestControls = EMPTY_CONTROLS;
258         responseControls = opCtx.getResponseControls();
259     }
260 
261 
262     /**
263      * Used to encapsulate [de]marshalling of controls before and after list operations.
264      * @param dn
265      * @param aliasDerefMode
266      * @param filter
267      * @param searchControls
268      * @return NamingEnumeration
269      */
270     protected EntryFilteringCursor doSearchOperation( LdapDN dn, AliasDerefMode aliasDerefMode,
271         ExprNode filter, SearchControls searchControls ) throws Exception
272     {
273         // setup the op context and populate with request controls
274         SearchOperationContext opCtx = new SearchOperationContext( session, dn, aliasDerefMode, filter,
275             searchControls );
276         opCtx.addRequestControls( requestControls );
277 
278         // execute search operation
279         EntryFilteringCursor results = service.getOperationManager().search( opCtx );
280 
281         // clear the request controls and set the response controls 
282         requestControls = EMPTY_CONTROLS;
283         responseControls = opCtx.getResponseControls();
284 
285         return results;
286     }
287 
288 
289     /**
290      * Used to encapsulate [de]marshalling of controls before and after list operations.
291      */
292     protected EntryFilteringCursor doListOperation( LdapDN target ) throws Exception
293     {
294         // setup the op context and populate with request controls
295         ListOperationContext opCtx = new ListOperationContext( session, target );
296         opCtx.addRequestControls( requestControls );
297 
298         // execute list operation
299         EntryFilteringCursor results = service.getOperationManager().list( opCtx );
300 
301         // clear the request controls and set the response controls 
302         requestControls = EMPTY_CONTROLS;
303         responseControls = opCtx.getResponseControls();
304 
305         return results;
306     }
307 
308 
309     protected ServerEntry doGetRootDSEOperation( LdapDN target ) throws Exception
310     {
311         GetRootDSEOperationContext opCtx = new GetRootDSEOperationContext( session, target );
312         opCtx.addRequestControls( requestControls );
313 
314         // do not reset request controls since this is not an external 
315         // operation and not do bother setting the response controls either
316         return service.getOperationManager().getRootDSE( opCtx );
317     }
318 
319 
320     /**
321      * Used to encapsulate [de]marshalling of controls before and after lookup operations.
322      */
323     protected ServerEntry doLookupOperation( LdapDN target ) throws Exception
324     {
325         // setup the op context and populate with request controls
326         LookupOperationContext opCtx;
327 
328         // execute lookup/getRootDSE operation
329         opCtx = new LookupOperationContext( session, target );
330         opCtx.addRequestControls( requestControls );
331         ServerEntry serverEntry = service.getOperationManager().lookup( opCtx );
332 
333         // clear the request controls and set the response controls 
334         requestControls = EMPTY_CONTROLS;
335         responseControls = opCtx.getResponseControls();
336         return serverEntry;
337     }
338 
339 
340     /**
341      * Used to encapsulate [de]marshalling of controls before and after lookup operations.
342      */
343     protected ServerEntry doLookupOperation( LdapDN target, String[] attrIds ) throws Exception
344     {
345         // setup the op context and populate with request controls
346         LookupOperationContext opCtx;
347 
348         // execute lookup/getRootDSE operation
349         opCtx = new LookupOperationContext( session, target, attrIds );
350         opCtx.addRequestControls( requestControls );
351         ClonedServerEntry serverEntry = service.getOperationManager().lookup( opCtx );
352 
353         // clear the request controls and set the response controls 
354         requestControls = EMPTY_CONTROLS;
355         responseControls = opCtx.getResponseControls();
356 
357         // Now remove the ObjectClass attribute if it has not been requested
358         if ( ( opCtx.getAttrsId() != null ) && ( opCtx.getAttrsId().size() != 0 ) )
359         {
360             if ( ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ) != null )
361                 && ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ).size() == 0 ) )
362             {
363                 serverEntry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
364             }
365         }
366 
367         return serverEntry;
368     }
369 
370 
371     /**
372      * Used to encapsulate [de]marshalling of controls before and after bind operations.
373      */
374     protected BindOperationContext doBindOperation( LdapDN bindDn, byte[] credentials, String saslMechanism, 
375         String saslAuthId ) throws Exception
376     {
377         // setup the op context and populate with request controls
378         BindOperationContext opCtx = new BindOperationContext( null );
379         opCtx.setDn( bindDn );
380         opCtx.setCredentials( credentials );
381         opCtx.setSaslMechanism( saslMechanism );
382         opCtx.setSaslAuthId( saslAuthId );
383         opCtx.addRequestControls( requestControls );
384 
385         // execute bind operation
386         service.getOperationManager().bind( opCtx );
387 
388         // clear the request controls and set the response controls 
389         requestControls = EMPTY_CONTROLS;
390         responseControls = opCtx.getResponseControls();
391         return opCtx;
392     }
393 
394 
395     /**
396      * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
397      */
398     protected void doMoveAndRenameOperation( LdapDN oldDn, LdapDN parent, String newRdn, boolean delOldDn )
399         throws Exception
400     {
401         // setup the op context and populate with request controls
402         MoveAndRenameOperationContext opCtx = new MoveAndRenameOperationContext( session, oldDn, parent, new Rdn(
403             newRdn ), delOldDn );
404         opCtx.addRequestControls( requestControls );
405 
406         // execute moveAndRename operation
407         service.getOperationManager().moveAndRename( opCtx );
408 
409         // clear the request controls and set the response controls 
410         requestControls = EMPTY_CONTROLS;
411         responseControls = opCtx.getResponseControls();
412     }
413 
414 
415     /**
416      * Used to encapsulate [de]marshalling of controls before and after modify operations.
417      */
418     protected void doModifyOperation( LdapDN dn, List<Modification> modifications ) throws Exception
419     {
420         // setup the op context and populate with request controls
421         ModifyOperationContext opCtx = new ModifyOperationContext( session, dn, modifications );
422         opCtx.addRequestControls( requestControls );
423 
424         // execute modify operation
425         service.getOperationManager().modify( opCtx );
426 
427         // clear the request controls and set the response controls 
428         requestControls = EMPTY_CONTROLS;
429         responseControls = opCtx.getResponseControls();
430     }
431 
432 
433     /**
434      * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
435      */
436     protected void doMove( LdapDN oldDn, LdapDN target ) throws Exception
437     {
438         // setup the op context and populate with request controls
439         MoveOperationContext opCtx = new MoveOperationContext( session, oldDn, target );
440         opCtx.addRequestControls( requestControls );
441 
442         // execute move operation
443         service.getOperationManager().move( opCtx );
444 
445         // clear the request controls and set the response controls 
446         requestControls = EMPTY_CONTROLS;
447         responseControls = opCtx.getResponseControls();
448     }
449 
450 
451     /**
452      * Used to encapsulate [de]marshalling of controls before and after rename operations.
453      */
454     protected void doRename( LdapDN oldDn, String newRdn, boolean delOldRdn ) throws Exception
455     {
456         // setup the op context and populate with request controls
457         RenameOperationContext opCtx = new RenameOperationContext( session, oldDn, new Rdn( newRdn ), delOldRdn );
458         opCtx.addRequestControls( requestControls );
459 
460         // execute rename operation
461         service.getOperationManager().rename( opCtx );
462 
463         // clear the request controls and set the response controls 
464         requestControls = EMPTY_CONTROLS;
465         responseControls = opCtx.getResponseControls();
466     }
467 
468     
469     public CoreSession getSession()
470     {
471         return session;
472     }
473     
474     
475     public DirectoryService getDirectoryService()
476     {
477         return service;
478     }
479     
480     
481     // ------------------------------------------------------------------------
482     // New Impl Specific Public Methods
483     // ------------------------------------------------------------------------
484 
485     /**
486      * Gets a handle on the root context of the DIT.  The RootDSE as the present user.
487      *
488      * @return the rootDSE context
489      * @throws NamingException if this fails
490      */
491     public abstract ServerContext getRootContext() throws NamingException;
492 
493 
494     /**
495      * Gets the {@link DirectoryService} associated with this context.
496      *
497      * @return the directory service associated with this context
498      */
499     public DirectoryService getService()
500     {
501         return service;
502     }
503 
504 
505     // ------------------------------------------------------------------------
506     // Protected Accessor Methods
507     // ------------------------------------------------------------------------
508 
509     
510     /**
511      * Gets the distinguished name of the entry associated with this Context.
512      * 
513      * @return the distinguished name of this Context's entry.
514      */
515     protected Name getDn()
516     {
517         return dn;
518     }
519 
520 
521     // ------------------------------------------------------------------------
522     // JNDI Context Interface Methods
523     // ------------------------------------------------------------------------
524 
525     /**
526      * @see javax.naming.Context#close()
527      */
528     public void close() throws NamingException
529     {
530         for ( DirectoryListener listener : listeners.values() )
531         {
532             try
533             {
534                 service.getEventService().removeListener( listener );
535             }
536             catch ( Exception e )
537             {
538                 JndiUtils.wrap( e );
539             }
540         }
541         
542         listeners.clear();
543     }
544 
545 
546     /**
547      * @see javax.naming.Context#getNameInNamespace()
548      */
549     public String getNameInNamespace() throws NamingException
550     {
551         return dn.getUpName();
552     }
553 
554 
555     /**
556      * @see javax.naming.Context#getEnvironment()
557      */
558     public Hashtable<String, Object> getEnvironment()
559     {
560         return env;
561     }
562 
563 
564     /**
565      * @see javax.naming.Context#addToEnvironment(java.lang.String, 
566      * java.lang.Object)
567      */
568     public Object addToEnvironment( String propName, Object propVal ) throws NamingException
569     {
570         return env.put( propName, propVal );
571     }
572 
573 
574     /**
575      * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
576      */
577     public Object removeFromEnvironment( String propName ) throws NamingException
578     {
579         return env.remove( propName );
580     }
581 
582 
583     /**
584      * @see javax.naming.Context#createSubcontext(java.lang.String)
585      */
586     public Context createSubcontext( String name ) throws NamingException
587     {
588         return createSubcontext( new LdapDN( name ) );
589     }
590 
591 
592     /**
593      * @see javax.naming.Context#createSubcontext(javax.naming.Name)
594      */
595     public Context createSubcontext( Name name ) throws NamingException
596     {
597         LdapDN target = buildTarget( name );
598         ServerEntry serverEntry = service.newEntry( target );
599         serverEntry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, JavaLdapSupport.JCONTAINER_ATTR );
600 
601         // Now add the CN attribute, which is mandatory
602         Rdn rdn = target.getRdn();
603 
604         if ( rdn != null )
605         {
606             if ( SchemaConstants.CN_AT.equals( rdn.getNormType() ) )
607             {
608                 serverEntry.put( rdn.getUpType(), ( String ) rdn.getUpValue() );
609             }
610             else
611             {
612                 // No CN in the rdn, this is an error
613                 throw new LdapSchemaViolationException( name
614                     + " does not contains the mandatory 'cn' attribute for JavaContainer ObjectClass!",
615                     ResultCodeEnum.OBJECT_CLASS_VIOLATION );
616             }
617         }
618         else
619         {
620             // No CN in the rdn, this is an error
621             throw new LdapSchemaViolationException( name
622                 + " does not contains the mandatory 'cn' attribute for JavaContainer ObjectClass!",
623                 ResultCodeEnum.OBJECT_CLASS_VIOLATION );
624         }
625 
626         /*
627          * Add the new context to the server which as a side effect adds 
628          * operational attributes to the serverEntry refering instance which
629          * can them be used to initialize a new ServerLdapContext.  Remember
630          * we need to copy over the controls as well to propagate the complete 
631          * environment besides what's in the hashtable for env.
632          */
633         try
634         {
635             doAddOperation( target, serverEntry );
636         }
637         catch ( Exception e )
638         {
639             JndiUtils.wrap( e );
640         }
641         
642         ServerLdapContext ctx = null;
643         
644         try
645         {
646             ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), target );
647         }
648         catch ( Exception e )
649         {
650             JndiUtils.wrap( e );
651         }
652         
653         return ctx;
654     }
655 
656 
657     /**
658      * @see javax.naming.Context#destroySubcontext(java.lang.String)
659      */
660     public void destroySubcontext( String name ) throws NamingException
661     {
662         destroySubcontext( new LdapDN( name ) );
663     }
664 
665 
666     /**
667      * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
668      */
669     public void destroySubcontext( Name name ) throws NamingException
670     {
671         LdapDN target = buildTarget( name );
672 
673         if ( target.size() == 0 )
674         {
675             throw new LdapNoPermissionException( "can't delete the rootDSE" );
676         }
677 
678         try
679         {
680             doDeleteOperation( target );
681         }
682         catch ( Exception e )
683         {
684             JndiUtils.wrap( e );
685         }
686     }
687 
688 
689     /**
690      * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
691      */
692     public void bind( String name, Object obj ) throws NamingException
693     {
694         bind( new LdapDN( name ), obj );
695     }
696 
697 
698     private void injectRdnAttributeValues( LdapDN target, ServerEntry serverEntry ) throws NamingException
699     {
700         // Add all the RDN attributes and their values to this entry
701         Rdn rdn = target.getRdn( target.size() - 1 );
702 
703         if ( rdn.size() == 1 )
704         {
705             serverEntry.put( rdn.getUpType(), ( String ) rdn.getValue() );
706         }
707         else
708         {
709             for ( AttributeTypeAndValue atav : rdn )
710             {
711                 serverEntry.put( atav.getUpType(), ( String ) atav.getNormValue() );
712             }
713         }
714     }
715 
716 
717     /**
718      * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
719      */
720     public void bind( Name name, Object obj ) throws NamingException
721     {
722         // First, use state factories to do a transformation
723         DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );
724 
725         LdapDN target = buildTarget( name );
726 
727         // let's be sure that the Attributes is case insensitive
728         ServerEntry outServerEntry = ServerEntryUtils.toServerEntry( AttributeUtils.toCaseInsensitive( res
729             .getAttributes() ), target, service.getRegistries() );
730 
731         if ( outServerEntry != null )
732         {
733             try
734             {
735                 doAddOperation( target, outServerEntry );
736             }
737             catch ( Exception e )
738             {
739                 JndiUtils.wrap( e );
740             }
741             return;
742         }
743 
744         if ( obj instanceof ServerEntry )
745         {
746             try
747             {
748                 doAddOperation( target, ( ServerEntry ) obj );
749             }
750             catch ( Exception e )
751             {
752                 JndiUtils.wrap( e );
753             }
754         }
755         // Check for Referenceable
756         else if ( obj instanceof Referenceable )
757         {
758             throw new NamingException( "Do not know how to store Referenceables yet!" );
759         }
760         // Store different formats
761         else if ( obj instanceof Reference )
762         {
763             // Store as ref and add outAttrs
764             throw new NamingException( "Do not know how to store References yet!" );
765         }
766         else if ( obj instanceof Serializable )
767         {
768             // Serialize and add outAttrs
769             ServerEntry serverEntry = service.newEntry( target );
770 
771             if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
772             {
773                 for ( EntryAttribute serverAttribute : outServerEntry )
774                 {
775                     serverEntry.put( serverAttribute );
776                 }
777             }
778 
779             // Get target and inject all rdn attributes into entry
780             injectRdnAttributeValues( target, serverEntry );
781 
782             // Serialize object into entry attributes and add it.
783             JavaLdapSupport.serialize( serverEntry, obj, service.getRegistries() );
784             try
785             {
786                 doAddOperation( target, serverEntry );
787             }
788             catch ( Exception e )
789             {
790                 JndiUtils.wrap( e );
791             }
792         }
793         else if ( obj instanceof DirContext )
794         {
795             // Grab attributes and merge with outAttrs
796             ServerEntry serverEntry = ServerEntryUtils.toServerEntry( ( ( DirContext ) obj ).getAttributes( "" ),
797                 target, service.getRegistries() );
798 
799             if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
800             {
801                 for ( EntryAttribute serverAttribute : outServerEntry )
802                 {
803                     serverEntry.put( serverAttribute );
804                 }
805             }
806 
807             injectRdnAttributeValues( target, serverEntry );
808             try
809             {
810                 doAddOperation( target, serverEntry );
811             }
812             catch ( Exception e )
813             {
814                 JndiUtils.wrap( e );
815             }
816         }
817         else
818         {
819             throw new NamingException( "Can't find a way to bind: " + obj );
820         }
821     }
822 
823 
824     /**
825      * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
826      */
827     public void rename( String oldName, String newName ) throws NamingException
828     {
829         rename( new LdapDN( oldName ), new LdapDN( newName ) );
830     }
831 
832 
833     /**
834      * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
835      */
836     public void rename( Name oldName, Name newName ) throws NamingException
837     {
838         LdapDN oldDn = buildTarget( oldName );
839         LdapDN newDn = buildTarget( newName );
840 
841         if ( oldDn.size() == 0 )
842         {
843             throw new LdapNoPermissionException( "can't rename the rootDSE" );
844         }
845 
846         // calculate parents
847         LdapDN oldBase = ( LdapDN ) oldName.clone();
848         oldBase.remove( oldName.size() - 1 );
849         LdapDN newBase = ( LdapDN ) newName.clone();
850         newBase.remove( newName.size() - 1 );
851 
852         String newRdn = newName.get( newName.size() - 1 );
853         String oldRdn = oldName.get( oldName.size() - 1 );
854         boolean delOldRdn = true;
855 
856         /*
857          * Attempt to use the java.naming.ldap.deleteRDN environment property
858          * to get an override for the deleteOldRdn option to modifyRdn.  
859          */
860         if ( null != env.get( DELETE_OLD_RDN_PROP ) )
861         {
862             String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
863             delOldRdn = !delOldRdnStr.equalsIgnoreCase( "false" ) && !delOldRdnStr.equalsIgnoreCase( "no" )
864                 && !delOldRdnStr.equals( "0" );
865         }
866 
867         /*
868          * We need to determine if this rename operation corresponds to a simple
869          * RDN name change or a move operation.  If the two names are the same
870          * except for the RDN then it is a simple modifyRdn operation.  If the
871          * names differ in size or have a different baseDN then the operation is
872          * a move operation.  Furthermore if the RDN in the move operation 
873          * changes it is both an RDN change and a move operation.
874          */
875         if ( ( oldName.size() == newName.size() ) && oldBase.equals( newBase ) )
876         {
877             try
878             {
879                 doRename( oldDn, newRdn, delOldRdn );
880             }
881             catch ( Exception e )
882             {
883                 JndiUtils.wrap( e );
884             }
885         }
886         else
887         {
888             LdapDN target = ( LdapDN ) newDn.clone();
889             target.remove( newDn.size() - 1 );
890 
891             if ( newRdn.equalsIgnoreCase( oldRdn ) )
892             {
893                 try
894                 {
895                     doMove( oldDn, target );
896                 }
897                 catch ( Exception e )
898                 {
899                     JndiUtils.wrap( e );
900                 }
901             }
902             else
903             {
904                 try
905                 {
906                     doMoveAndRenameOperation( oldDn, target, newRdn, delOldRdn );
907                 }
908                 catch ( Exception e )
909                 {
910                     JndiUtils.wrap( e );
911                 }
912             }
913         }
914     }
915 
916 
917     /**
918      * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
919      */
920     public void rebind( String name, Object obj ) throws NamingException
921     {
922         rebind( new LdapDN( name ), obj );
923     }
924 
925 
926     /**
927      * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
928      */
929     public void rebind( Name name, Object obj ) throws NamingException
930     {
931         LdapDN target = buildTarget( name );
932 
933         try
934         {
935             if ( service.getOperationManager().hasEntry( new EntryOperationContext( session, target ) ) )
936             {
937                 doDeleteOperation( target );
938             }
939         }
940         catch ( Exception e )
941         {
942             JndiUtils.wrap( e );
943         }
944 
945         bind( name, obj );
946     }
947 
948 
949     /**
950      * @see javax.naming.Context#unbind(java.lang.String)
951      */
952     public void unbind( String name ) throws NamingException
953     {
954         unbind( new LdapDN( name ) );
955     }
956 
957 
958     /**
959      * @see javax.naming.Context#unbind(javax.naming.Name)
960      */
961     public void unbind( Name name ) throws NamingException
962     {
963         try
964         {
965             doDeleteOperation( buildTarget( name ) );
966         }
967         catch ( Exception e )
968         {
969             JndiUtils.wrap( e );
970         }
971     }
972 
973 
974     /**
975      * @see javax.naming.Context#lookup(java.lang.String)
976      */
977     public Object lookup( String name ) throws NamingException
978     {
979         if ( StringTools.isEmpty( name ) )
980         {
981             return lookup( LdapDN.EMPTY_LDAPDN );
982         }
983         else
984         {
985             return lookup( new LdapDN( name ) );
986         }
987     }
988 
989 
990     /**
991      * @see javax.naming.Context#lookup(javax.naming.Name)
992      */
993     public Object lookup( Name name ) throws NamingException
994     {
995         Object obj;
996         LdapDN target = buildTarget( name );
997 
998         ServerEntry serverEntry = null;
999 
1000         try
1001         {
1002             if ( name.size() == 0 )
1003             {
1004                 serverEntry = doGetRootDSEOperation( target );
1005             }
1006             else
1007             {
1008                 serverEntry = doLookupOperation( target );
1009             }
1010         }
1011         catch ( Exception e )
1012         {
1013             JndiUtils.wrap( e );
1014         }
1015 
1016         try
1017         {
1018             obj = DirectoryManager.getObjectInstance( null, name, this, env, 
1019                 ServerEntryUtils.toBasicAttributes( serverEntry ) );
1020         }
1021         catch ( Exception e )
1022         {
1023             String msg = "Failed to create an object for " + target;
1024             msg += " using object factories within the context's environment.";
1025             NamingException ne = new NamingException( msg );
1026             ne.setRootCause( e );
1027             throw ne;
1028         }
1029 
1030         if ( obj != null )
1031         {
1032             return obj;
1033         }
1034 
1035         // First lets test and see if the entry is a serialized java object
1036         if ( serverEntry.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
1037         {
1038             // Give back serialized object and not a context
1039             return JavaLdapSupport.deserialize( serverEntry );
1040         }
1041 
1042         // Initialize and return a context since the entry is not a java object
1043         ServerLdapContext ctx = null;
1044         
1045         try
1046         {
1047             ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), target );
1048         }
1049         catch ( Exception e )
1050         {
1051             JndiUtils.wrap( e );
1052         }
1053         
1054         return ctx;
1055     }
1056 
1057 
1058     /**
1059      * @see javax.naming.Context#lookupLink(java.lang.String)
1060      */
1061     public Object lookupLink( String name ) throws NamingException
1062     {
1063         throw new UnsupportedOperationException();
1064     }
1065 
1066 
1067     /**
1068      * @see javax.naming.Context#lookupLink(javax.naming.Name)
1069      */
1070     public Object lookupLink( Name name ) throws NamingException
1071     {
1072         throw new UnsupportedOperationException();
1073     }
1074 
1075 
1076     /**
1077      * Non-federated implementation presuming the name argument is not a 
1078      * composite name spanning multiple namespaces but a compound name in 
1079      * the same LDAP namespace.  Hence the parser returned is always the
1080      * same as calling this method with the empty String. 
1081      * 
1082      * @see javax.naming.Context#getNameParser(java.lang.String)
1083      */
1084     public NameParser getNameParser( String name ) throws NamingException
1085     {
1086         return new NameParser()
1087         {
1088             public Name parse( String name ) throws NamingException
1089             {
1090                 return new LdapDN( name );
1091             }
1092         };
1093     }
1094 
1095 
1096     /**
1097      * Non-federated implementation presuming the name argument is not a 
1098      * composite name spanning multiple namespaces but a compound name in 
1099      * the same LDAP namespace.  Hence the parser returned is always the
1100      * same as calling this method with the empty String Name.
1101      * 
1102      * @see javax.naming.Context#getNameParser(javax.naming.Name)
1103      */
1104     public NameParser getNameParser( Name name ) throws NamingException
1105     {
1106         return new NameParser()
1107         {
1108             public Name parse( String name ) throws NamingException
1109             {
1110                 return new LdapDN( name );
1111             }
1112         };
1113     }
1114 
1115 
1116     /**
1117      * @see javax.naming.Context#list(java.lang.String)
1118      */
1119     @SuppressWarnings(value =
1120         { "unchecked" })
1121     public NamingEnumeration list( String name ) throws NamingException
1122     {
1123         return list( new LdapDN( name ) );
1124     }
1125 
1126 
1127     /**
1128      * @see javax.naming.Context#list(javax.naming.Name)
1129      */
1130     @SuppressWarnings(value =
1131         { "unchecked" })
1132     public NamingEnumeration list( Name name ) throws NamingException
1133     {
1134         try
1135         {
1136             return new NamingEnumerationAdapter( doListOperation( buildTarget( name ) ) );
1137         }
1138         catch ( Exception e )
1139         {
1140             JndiUtils.wrap( e );
1141             return null; // shut up compiler
1142         }
1143     }
1144 
1145 
1146     /**
1147      * @see javax.naming.Context#listBindings(java.lang.String)
1148      */
1149     @SuppressWarnings(value =
1150         { "unchecked" })
1151     public NamingEnumeration listBindings( String name ) throws NamingException
1152     {
1153         return listBindings( new LdapDN( name ) );
1154     }
1155 
1156 
1157     /**
1158      * @see javax.naming.Context#listBindings(javax.naming.Name)
1159      */
1160     @SuppressWarnings(value =
1161         { "unchecked" })
1162     public NamingEnumeration listBindings( Name name ) throws NamingException
1163     {
1164         // Conduct a special one level search at base for all objects
1165         LdapDN base = buildTarget( name );
1166         PresenceNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
1167         SearchControls ctls = new SearchControls();
1168         ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1169         AliasDerefMode aliasDerefMode = AliasDerefMode.getEnum( getEnvironment() );
1170         try
1171         {
1172             return new NamingEnumerationAdapter( doSearchOperation( base, aliasDerefMode, filter, ctls ) );
1173         }
1174         catch ( Exception e )
1175         {
1176             JndiUtils.wrap( e );
1177             return null; // shutup compiler
1178         }
1179     }
1180 
1181 
1182     /**
1183      * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
1184      */
1185     public String composeName( String name, String prefix ) throws NamingException
1186     {
1187         return composeName( new LdapDN( name ), new LdapDN( prefix ) ).toString();
1188     }
1189 
1190 
1191     /**
1192      * @see javax.naming.Context#composeName(javax.naming.Name,
1193      * javax.naming.Name)
1194      */
1195     public Name composeName( Name name, Name prefix ) throws NamingException
1196     {
1197         // No prefix reduces to name, or the name relative to this context
1198         if ( prefix == null || prefix.size() == 0 )
1199         {
1200             return name;
1201         }
1202 
1203         /*
1204          * Example: This context is ou=people and say name is the relative
1205          * name of uid=jwalker and the prefix is dc=domain.  Then we must
1206          * compose the name relative to prefix which would be:
1207          * 
1208          * uid=jwalker,ou=people,dc=domain.
1209          * 
1210          * The following general algorithm generates the right name:
1211          *      1). Find the Dn for name and walk it from the head to tail
1212          *          trying to match for the head of prefix.
1213          *      2). Remove name components from the Dn until a match for the 
1214          *          head of the prefix is found.
1215          *      3). Return the remainder of the fqn or Dn after chewing off some
1216          */
1217 
1218         // 1). Find the Dn for name and walk it from the head to tail
1219         Name fqn = buildTarget( name );
1220         String head = prefix.get( 0 );
1221 
1222         // 2). Walk the fqn trying to match for the head of the prefix
1223         while ( fqn.size() > 0 )
1224         {
1225             // match found end loop
1226             if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
1227             {
1228                 return fqn;
1229             }
1230             else
1231             // 2). Remove name components from the Dn until a match 
1232             {
1233                 fqn.remove( 0 );
1234             }
1235         }
1236 
1237         String msg = "The prefix '" + prefix + "' is not an ancestor of this ";
1238         msg += "entry '" + dn + "'";
1239         throw new NamingException( msg );
1240     }
1241 
1242 
1243     // ------------------------------------------------------------------------
1244     // EventContext implementations
1245     // ------------------------------------------------------------------------
1246 
1247     public void addNamingListener( Name name, int scope, NamingListener namingListener ) throws NamingException
1248     {
1249         ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
1250 
1251         try
1252         {
1253             DirectoryListener listener = new EventListenerAdapter( ( ServerLdapContext ) this, namingListener );
1254             NotificationCriteria criteria = new NotificationCriteria();
1255             criteria.setFilter( filter );
1256             criteria.setScope( SearchScope.getSearchScope( scope ) );
1257             criteria.setAliasDerefMode( AliasDerefMode.getEnum( env ) );
1258             criteria.setBase( buildTarget( name ) );
1259             
1260             service.getEventService().addListener( listener );
1261             listeners.put( namingListener, listener );
1262         }
1263         catch ( Exception e )
1264         {
1265             JndiUtils.wrap( e );
1266         }
1267     }
1268 
1269 
1270     public void addNamingListener( String name, int scope, NamingListener namingListener ) throws NamingException
1271     {
1272         addNamingListener( new LdapDN( name ), scope, namingListener );
1273     }
1274 
1275 
1276     public void removeNamingListener( NamingListener namingListener ) throws NamingException
1277     {
1278         try
1279         {
1280             DirectoryListener listener = listeners.remove( namingListener );
1281             
1282             if ( listener != null )
1283             {
1284                 service.getEventService().removeListener( listener );
1285             }
1286         }
1287         catch ( Exception e )
1288         {
1289             JndiUtils.wrap( e );
1290         }
1291     }
1292 
1293 
1294     public boolean targetMustExist() throws NamingException
1295     {
1296         return false;
1297     }
1298 
1299 
1300     /**
1301      * Allows subclasses to register and unregister listeners.
1302      *
1303      * @return the set of listeners used for tracking registered name listeners.
1304      */
1305     protected Map<NamingListener, DirectoryListener> getListeners()
1306     {
1307         return listeners;
1308     }
1309 
1310 
1311     // ------------------------------------------------------------------------
1312     // Utility Methods to Reduce Code
1313     // ------------------------------------------------------------------------
1314 
1315     /**
1316      * Clones this context's DN and adds the components of the name relative to 
1317      * this context to the left hand side of this context's cloned DN. 
1318      * 
1319      * @param relativeName a name relative to this context.
1320      * @return the name of the target
1321      * @throws InvalidNameException if relativeName is not a valid name in
1322      *      the LDAP namespace.
1323      */
1324     LdapDN buildTarget( Name relativeName ) throws InvalidNameException
1325     {
1326         LdapDN target = ( LdapDN ) dn.clone();
1327 
1328         // Add to left hand side of cloned DN the relative name arg
1329         target.addAllNormalized( target.size(), relativeName );
1330         return target;
1331     }
1332 }