001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.core.referral;
021    
022    import javax.naming.Context;
023    import javax.naming.NamingException;
024    
025    import org.apache.directory.server.core.DirectoryService;
026    import org.apache.directory.server.core.ReferralManager;
027    import org.apache.directory.server.core.ReferralManagerImpl;
028    import org.apache.directory.server.core.interceptor.BaseInterceptor;
029    import org.apache.directory.server.core.interceptor.NextInterceptor;
030    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
031    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
032    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
033    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
034    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
035    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
036    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
037    import org.apache.directory.server.core.partition.PartitionNexus;
038    import org.apache.directory.server.i18n.I18n;
039    import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
040    import org.apache.directory.shared.ldap.constants.SchemaConstants;
041    import org.apache.directory.shared.ldap.entry.StringValue;
042    import org.apache.directory.shared.ldap.entry.EntryAttribute;
043    import org.apache.directory.shared.ldap.entry.ServerEntry;
044    import org.apache.directory.shared.ldap.entry.Value;
045    import org.apache.directory.shared.ldap.filter.SearchScope;
046    import org.apache.directory.shared.ldap.name.DN;
047    import org.apache.directory.shared.ldap.schema.SchemaManager;
048    import org.apache.directory.shared.ldap.util.LdapURL;
049    import org.apache.directory.shared.ldap.util.StringTools;
050    import org.slf4j.Logger;
051    import org.slf4j.LoggerFactory;
052    
053    
054    /**
055     * An service which is responsible referral handling behavoirs.  It manages 
056     * referral handling behavoir when the {@link Context#REFERRAL} is implicitly
057     * or explicitly set to "ignore", when set to "throw" and when set to "follow". 
058     * 
059     * @org.apache.xbean.XBean
060     *
061     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062     * @version $Rev$
063     */
064    public class ReferralInterceptor extends BaseInterceptor
065    {
066        private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class );
067    
068        private PartitionNexus nexus;
069    
070        /** The global schemaManager */
071        private SchemaManager schemaManager;
072    
073        /** The referralManager */
074        private ReferralManager referralManager;
075    
076        /** A normalized form for the SubschemaSubentry DN */
077        private String subschemaSubentryDnNorm;
078    
079        
080        static private void checkRefAttributeValue( Value<?> value ) throws NamingException, LdapURLEncodingException
081        {
082            StringValue ref = ( StringValue ) value;
083    
084            String refVal = ref.getString();
085    
086            LdapURL ldapUrl = new LdapURL( refVal );
087    
088            // We have a LDAP URL, we have to check that :
089            // - we don't have scope specifier
090            // - we don't have filters
091            // - we don't have attribute description list
092            // - we don't have extensions
093            // - the DN is not empty
094    
095            if ( ldapUrl.getScope() != SearchScope.OBJECT )
096            {
097                // This is the default value if we don't have any scope
098                // Let's assume that it's incorrect if we get something
099                // else in the LdapURL
100                String message = I18n.err( I18n.ERR_36 );
101                LOG.error( message );
102                throw new NamingException( message );
103            }
104    
105            if ( !StringTools.isEmpty( ldapUrl.getFilter() ) )
106            {
107                String message = I18n.err( I18n.ERR_37 );
108                LOG.error( message );
109                throw new NamingException( message );
110            }
111    
112            if ( ( ldapUrl.getAttributes() != null ) && ( ldapUrl.getAttributes().size() != 0 ) )
113            {
114                String message = I18n.err( I18n.ERR_38 );
115                LOG.error( message );
116                throw new NamingException( message );
117            }
118    
119            if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) )
120            {
121                String message = I18n.err( I18n.ERR_39 );
122                LOG.error( message );
123                throw new NamingException( message );
124            }
125    
126            if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) )
127            {
128                String message = I18n.err( I18n.ERR_40 );
129                LOG.error( message );
130                throw new NamingException( message );
131            }
132    
133            DN dn = ldapUrl.getDn();
134    
135            if ( ( dn == null ) || dn.isEmpty() )
136            {
137                String message = I18n.err( I18n.ERR_41 );
138                LOG.error( message );
139                throw new NamingException( message );
140            }
141        }
142    
143        
144        static private boolean isReferral( ServerEntry entry ) throws NamingException
145        {
146            // Check that the entry is not null, otherwise return FALSE.
147            // This is typically to cover the case where the entry has not 
148            // been added into the context because it does not exists.
149            if ( entry == null )
150            {
151                return false;
152            }
153            
154            EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
155    
156            if ( oc == null )
157            {
158                LOG.warn( "could not find objectClass attribute in entry: " + entry );
159                return false;
160            }
161    
162            if ( !oc.contains( SchemaConstants.REFERRAL_OC ) )
163            {
164                return false;
165            }
166            else
167            {
168                // We have a referral ObjectClass, let's check that the ref is
169                // valid, accordingly to the RFC
170    
171                // Get the 'ref' attributeType
172                EntryAttribute refAttr = entry.get( SchemaConstants.REF_AT );
173    
174                if ( refAttr == null )
175                {
176                    // very unlikely, as we have already checked the entry in SchemaInterceptor
177                    String message = I18n.err( I18n.ERR_42 );
178                    LOG.error( message );
179                    throw new NamingException( message );
180                }
181    
182                for ( Value<?> value : refAttr )
183                {
184                    try
185                    {
186                        checkRefAttributeValue( value );
187                    }
188                    catch ( LdapURLEncodingException luee )
189                    {
190                        // Either the URL is invalid, or it's not a LDAP URL.
191                        // we will just ignore this LdapURL.
192                    }
193                }
194    
195                return true;
196            }
197        }
198    
199    
200        public void init( DirectoryService directoryService ) throws Exception
201        {
202            nexus = directoryService.getPartitionNexus();
203            schemaManager = directoryService.getSchemaManager();
204    
205            // Initialize the referralManager
206            referralManager = new ReferralManagerImpl( directoryService );
207            directoryService.setReferralManager( referralManager );
208    
209            Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
210            DN subschemaSubentryDn = new DN( subschemaSubentry.getString() );
211            subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() );
212            subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
213        }
214    
215    
216        /**
217         * Add an entry into the server. We have 3 cases :
218         * (1) The entry does not have any parent referral and is not a referral itself
219         * (2) The entry does not have any parent referral and is a referral itself
220         * (3) The entry has a parent referral
221         * 
222         * Case (1) is easy : we inject the entry into the server and we are done.
223         * Case (2) is the same as case (1), but we have to update the referral manager.
224         * Case (3) is handled by the LdapProcotol handler, as we have to return a 
225         * LdapResult containing a list of this entry's parent's referrals URL, if the 
226         * ManageDSAIT control is not present, or the parent's entry if the control 
227         * is present. 
228         * 
229         * Of course, if the entry already exists, nothing will be done, as we will get an
230         * entryAlreadyExists error.
231         *  
232         */
233        public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
234        {
235            ServerEntry entry = opContext.getEntry();
236            
237            // Check if the entry is a referral itself
238            boolean isReferral = isReferral( entry );
239    
240            // We add the entry into the server
241            next.add( opContext );
242            
243            // If the addition is successful, we update the referralManager 
244            if ( isReferral )
245            {
246                // We have to add it to the referralManager
247                referralManager.lockWrite();
248    
249                referralManager.addReferral( entry );
250    
251                referralManager.unlock();
252            }
253    
254        }
255    
256    
257        /**
258         * Delete an entry in the server. We have 4 cases :
259         * (1) the entry is not a referral and does not have a parent referral
260         * (2) the entry is not a referral but has a parent referral
261         * (3) the entry is a referral
262         * 
263         * Case (1) is handled by removing the entry from the server
264         * In case (2), we return an exception build using the parent referral 
265         * For case(3), we remove the entry from the server and remove the referral
266         * from the referral manager.
267         * 
268         * If the entry does not exist in the server, we will get a NoSuchObject error
269         */
270        public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
271        {
272            ServerEntry entry = opContext.getEntry();
273    
274            // First delete the entry into the server
275            next.delete( opContext );
276            
277            // Check if the entry exists and is a referral itself
278            // If so, we have to update the referralManager
279            if ( ( entry != null ) && isReferral( entry ) )
280            {
281                // We have to remove it from the referralManager
282                referralManager.lockWrite();
283    
284                referralManager.removeReferral( entry );
285    
286                referralManager.unlock();
287            }
288        }
289    
290    
291        /**
292         * 
293         **/
294        public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
295        {
296            DN oldName = opContext.getDn();
297    
298            DN newName = ( DN ) opContext.getParent().clone();
299            newName.add( oldName.get( oldName.size() - 1 ) );
300    
301            // Check if the entry is a referral itself
302            boolean isReferral = isReferral( opContext.getEntry() );
303    
304            next.move( opContext );
305            
306            
307            if ( isReferral ) 
308            {
309                // Update the referralManager
310                LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName );
311                
312                ServerEntry newEntry = nexus.lookup( lookupContext );
313                
314                referralManager.lockWrite();
315                
316                referralManager.addReferral( newEntry );
317                referralManager.removeReferral( opContext.getEntry() );
318                
319                referralManager.unlock();
320            }
321        }
322    
323    
324        /**
325         * 
326         **/
327        public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws Exception
328        {
329            DN newName = ( DN ) opContext.getParent().clone();
330            newName.add( opContext.getNewRdn() );
331    
332            // Check if the entry is a referral itself
333            boolean isReferral = isReferral( opContext.getEntry() );
334    
335            next.moveAndRename( opContext );
336            
337            if ( isReferral ) 
338            {
339                // Update the referralManager
340                LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName );
341                
342                ServerEntry newEntry = nexus.lookup( lookupContext );
343                
344                referralManager.lockWrite();
345                
346                referralManager.addReferral( newEntry );
347                referralManager.removeReferral( opContext.getEntry() );
348                
349                referralManager.unlock();
350            }
351        }
352    
353    
354        public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
355        {
356            // Check if the entry is a referral itself
357            boolean isReferral = isReferral( opContext.getEntry() );
358    
359            next.rename( opContext );
360            
361            if ( isReferral ) 
362            {
363                // Update the referralManager
364                LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), opContext.getNewDn() );
365                
366                ServerEntry newEntry = nexus.lookup( lookupContext );
367                
368                referralManager.lockWrite();
369                
370                referralManager.addReferral( newEntry );
371                referralManager.removeReferral( opContext.getEntry().getOriginalEntry() );
372                
373                referralManager.unlock();
374            }
375        }
376        
377    
378        /**
379         * Modify an entry in the server.
380         */
381        public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
382        {
383            DN name = opContext.getDn();
384            
385            // handle a normal modify without following referrals
386            next.modify( opContext );
387    
388            // Check if we are trying to modify the schema or the rootDSE,
389            // if so, we don't modify the referralManager
390            if ( ( name == DN.EMPTY_DN ) || ( subschemaSubentryDnNorm.equals( name.getNormName() ) ) )
391            {
392                // Do nothing
393                return;
394            }
395    
396            // Update the referralManager. We have to read the entry again
397            // as it has been modified, before updating the ReferralManager
398            // TODO: this can be spare, as we build the entry later.
399            // But we will have to store the modified entry into the opContext
400            LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), name );
401            
402            ServerEntry newEntry = nexus.lookup( lookupContext );
403    
404            // Check that we have the entry, just in case
405            // TODO : entries should be locked until the operation is done on it.
406            if ( newEntry != null )
407            {
408                referralManager.lockWrite();
409    
410                if ( referralManager.isReferral( newEntry.getDn() ) )
411                {
412                    referralManager.removeReferral( opContext.getEntry() );
413                    referralManager.addReferral( newEntry );
414                }
415                
416                referralManager.unlock();
417            }
418        }
419    
420    
421        /**
422         * When adding a new context partition, we have to update the referralManager
423         * by injecting all the new referrals into it. This is done using the init()
424         * method of the referralManager.
425         *
426        public void addContextPartition( NextInterceptor next, AddContextPartitionOperationContext opContext )
427            throws Exception
428        {
429            // First, inject the partition
430            next.addContextPartition( opContext );
431    
432            Partition partition = opContext.getPartition();
433            DN suffix = partition.getSuffixDn();
434            
435            // add referrals immediately after adding the new partition
436            referralManager.init( directoryService, new String[]{ suffix.getNormName() } );
437        }
438    
439    
440        /**
441         * Remove a partion's referrals from the server. We have to first
442         * clear the referrals manager from all of this partition's referrals,
443         * then we can delete the partition.
444         *
445        public void removeContextPartition( NextInterceptor next, RemoveContextPartitionOperationContext opContext )
446            throws Exception
447        {
448            // get the partition suffix
449            DN suffix = opContext.getDn();
450    
451            // remove referrals immediately before removing the partition
452            referralManager.remove( directoryService, suffix );
453    
454            // And remove the partition from the server
455            next.removeContextPartition( opContext );
456        }*/
457    }