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.referral;
21  
22  
23  import javax.naming.Context;
24  
25  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
26  import org.apache.directory.api.ldap.model.entry.Attribute;
27  import org.apache.directory.api.ldap.model.entry.Entry;
28  import org.apache.directory.api.ldap.model.entry.Value;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException;
31  import org.apache.directory.api.ldap.model.message.SearchScope;
32  import org.apache.directory.api.ldap.model.name.Dn;
33  import org.apache.directory.api.ldap.model.url.LdapUrl;
34  import org.apache.directory.api.util.Strings;
35  import org.apache.directory.server.core.api.DirectoryService;
36  import org.apache.directory.server.core.api.InterceptorEnum;
37  import org.apache.directory.server.core.api.ReferralManager;
38  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
39  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
40  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
41  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
42  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
43  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
44  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
45  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
46  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
47  import org.apache.directory.server.core.api.partition.PartitionNexus;
48  import org.apache.directory.server.core.shared.ReferralManagerImpl;
49  import org.apache.directory.server.i18n.I18n;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  
54  /**
55   * An service which is responsible referral handling behaviors.  It manages
56   * referral handling behavior when the {@link Context#REFERRAL} is implicitly
57   * or explicitly set to "ignore", when set to "throw" and when set to "follow".
58   * 
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   */
61  public class ReferralInterceptor extends BaseInterceptor
62  {
63      private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class );
64  
65      private PartitionNexus nexus;
66  
67      /** The referralManager */
68      private ReferralManager referralManager;
69  
70      /** A normalized form for the SubschemaSubentry Dn */
71      private Dn subschemaSubentryDn;
72  
73  
74      /**
75       * Creates a new instance of a ReferralInterceptor.
76       */
77      public ReferralInterceptor()
78      {
79          super( InterceptorEnum.REFERRAL_INTERCEPTOR );
80      }
81  
82  
83      private static void checkRefAttributeValue( Value value ) throws LdapException
84      {
85          String refVal = value.getString();
86  
87          LdapUrl ldapUrl = new LdapUrl( refVal );
88  
89          // We have a LDAP URL, we have to check that :
90          // - we don't have scope specifier
91          // - we don't have filters
92          // - we don't have attribute description list
93          // - we don't have extensions
94          // - the Dn is not empty
95  
96          if ( ldapUrl.getScope() != SearchScope.OBJECT )
97          {
98              // This is the default value if we don't have any scope
99              // Let's assume that it's incorrect if we get something
100             // else in the LdapURL
101             String message = I18n.err( I18n.ERR_36 );
102             LOG.error( message );
103             throw new LdapException( message );
104         }
105 
106         if ( !Strings.isEmpty( ldapUrl.getFilter() ) )
107         {
108             String message = I18n.err( I18n.ERR_37 );
109             LOG.error( message );
110             throw new LdapException( message );
111         }
112 
113         if ( ( ldapUrl.getAttributes() != null ) && !ldapUrl.getAttributes().isEmpty() )
114         {
115             String message = I18n.err( I18n.ERR_38 );
116             LOG.error( message );
117             throw new LdapException( message );
118         }
119 
120         if ( ( ldapUrl.getExtensions() != null ) && !ldapUrl.getExtensions().isEmpty() )
121         {
122             String message = I18n.err( I18n.ERR_39 );
123             LOG.error( message );
124             throw new LdapException( message );
125         }
126 
127         if ( ( ldapUrl.getExtensions() != null ) && !ldapUrl.getExtensions().isEmpty() )
128         {
129             String message = I18n.err( I18n.ERR_40 );
130             LOG.error( message );
131             throw new LdapException( message );
132         }
133 
134         Dn dn = ldapUrl.getDn();
135 
136         if ( ( dn == null ) || dn.isEmpty() )
137         {
138             String message = I18n.err( I18n.ERR_41 );
139             LOG.error( message );
140             throw new LdapException( message );
141         }
142     }
143 
144 
145     // This will suppress PMD.EmptyCatchBlock warnings in this method
146     @SuppressWarnings("PMD.EmptyCatchBlock")
147     private boolean isReferral( Entry entry ) throws LdapException
148     {
149         // Check that the entry is not null, otherwise return FALSE.
150         // This is typically to cover the case where the entry has not
151         // been added into the context because it does not exists.
152         if ( entry == null )
153         {
154             return false;
155         }
156 
157         if ( !entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.REFERRAL_OC ) )
158         {
159             return false;
160         }
161         else
162         {
163             // We have a referral ObjectClass, let's check that the ref is
164             // valid, accordingly to the RFC
165 
166             // Get the 'ref' attributeType
167             Attribute refAttr = entry.get( SchemaConstants.REF_AT );
168 
169             if ( refAttr == null )
170             {
171                 // very unlikely, as we have already checked the entry in SchemaInterceptor
172                 String message = I18n.err( I18n.ERR_42 );
173                 LOG.error( message );
174                 throw new LdapException( message );
175             }
176 
177             for ( Value value : refAttr )
178             {
179                 try
180                 {
181                     checkRefAttributeValue( value );
182                 }
183                 catch ( LdapURLEncodingException luee )
184                 {
185                     // Either the URL is invalid, or it's not a LDAP URL.
186                     // we will just ignore this LdapURL.
187                 }
188             }
189 
190             return true;
191         }
192     }
193 
194 
195     @Override
196     public void init( DirectoryService directoryService ) throws LdapException
197     {
198         super.init( directoryService );
199 
200         nexus = directoryService.getPartitionNexus();
201 
202         // Initialize the referralManager
203         referralManager = new ReferralManagerImpl( directoryService );
204         directoryService.setReferralManager( referralManager );
205 
206         Value subschemaSubentry = nexus.getRootDseValue( directoryService.getAtProvider().getSubschemaSubentry() );
207         subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() );
208     }
209 
210 
211     /**
212      * Add an entry into the server. We have 3 cases :
213      * (1) The entry does not have any parent referral and is not a referral itself
214      * (2) The entry does not have any parent referral and is a referral itself
215      * (3) The entry has a parent referral
216      * 
217      * Case (1) is easy : we inject the entry into the server and we are done.
218      * Case (2) is the same as case (1), but we have to update the referral manager.
219      * Case (3) is handled by the LdapProcotol handler, as we have to return a
220      * LdapResult containing a list of this entry's parent's referrals URL, if the
221      * ManageDSAIT control is not present, or the parent's entry if the control
222      * is present.
223      * 
224      * Of course, if the entry already exists, nothing will be done, as we will get an
225      * entryAlreadyExists error.
226      * 
227      */
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     public void add( AddOperationContext addContext ) throws LdapException
233     {
234         Entry entry = addContext.getEntry();
235 
236         // Check if the entry is a referral itself
237         boolean isReferral = isReferral( entry );
238 
239         // We add the entry into the server
240         next( addContext );
241 
242         // If the addition is successful, we update the referralManager
243         if ( isReferral )
244         {
245             // We have to add it to the referralManager
246             referralManager.lockWrite();
247 
248             try
249             {
250                 referralManager.addReferral( entry );
251             }
252             finally
253             {
254                 referralManager.unlock();
255             }
256         }
257     }
258 
259 
260     /**
261      * Delete an entry in the server. We have 4 cases :
262      * (1) the entry is not a referral and does not have a parent referral
263      * (2) the entry is not a referral but has a parent referral
264      * (3) the entry is a referral
265      * 
266      * Case (1) is handled by removing the entry from the server
267      * In case (2), we return an exception build using the parent referral
268      * For case(3), we remove the entry from the server and remove the referral
269      * from the referral manager.
270      * 
271      * If the entry does not exist in the server, we will get a NoSuchObject error
272      */
273     /**
274      * {@inheritDoc}
275      */
276     @Override
277     public void delete( DeleteOperationContext deleteContext ) throws LdapException
278     {
279         // First delete the entry into the server
280         next( deleteContext );
281 
282         Entry entry = deleteContext.getEntry();
283 
284         // Check if the entry exists and is a referral itself
285         // If so, we have to update the referralManager
286         if ( ( entry != null ) && isReferral( entry ) )
287         {
288             // We have to remove it from the referralManager
289             referralManager.lockWrite();
290 
291             try
292             {
293                 referralManager.removeReferral( entry );
294             }
295             finally
296             {
297                 referralManager.unlock();
298             }
299         }
300     }
301 
302 
303     /**
304      * {@inheritDoc}
305      */
306     @Override
307     public void modify( ModifyOperationContext modifyContext ) throws LdapException
308     {
309         Dn dn = modifyContext.getDn();
310 
311         // handle a normal modify without following referrals
312         next( modifyContext );
313 
314         // Check if we are trying to modify the schema or the rootDSE,
315         // if so, we don't modify the referralManager
316         if ( dn.isEmpty() || dn.equals( subschemaSubentryDn ) )
317         {
318             // Do nothing
319             return;
320         }
321 
322         // Update the referralManager. We have to read the entry again
323         // as it has been modified, before updating the ReferralManager
324         // TODO: this can be spare, as we already have the altered entry
325         // into the opContext, but for an unknow reason, this will fail
326         // on eferral tests...
327         LookupOperationContext lookupContext =
328             new LookupOperationContext( modifyContext.getSession(), dn, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
329         lookupContext.setPartition( modifyContext.getPartition() );
330         lookupContext.setTransaction( modifyContext.getTransaction() );
331 
332         Entry newEntry = nexus.lookup( lookupContext );
333 
334         // Update the referralManager.
335         // Check that we have the entry, just in case
336         // TODO : entries should be locked until the operation is done on it.
337         if ( newEntry != null )
338         {
339             referralManager.lockWrite();
340 
341             try
342             {
343                 if ( referralManager.isReferral( newEntry.getDn() ) )
344                 {
345                     referralManager.removeReferral( modifyContext.getEntry() );
346                     referralManager.addReferral( newEntry );
347                 }
348             }
349             finally
350             {
351                 referralManager.unlock();
352             }
353         }
354     }
355 
356 
357     /**
358      * {@inheritDoc}
359      **/
360     @Override
361     public void move( MoveOperationContext moveContext ) throws LdapException
362     {
363         // Check if the entry is a referral itself
364         boolean isReferral = isReferral( moveContext.getOriginalEntry() );
365 
366         next( moveContext );
367 
368         if ( isReferral )
369         {
370             // Update the referralManager
371             referralManager.lockWrite();
372 
373             try
374             {
375                 referralManager.addReferral( moveContext.getModifiedEntry() );
376                 referralManager.removeReferral( moveContext.getOriginalEntry() );
377             }
378             finally
379             {
380                 referralManager.unlock();
381             }
382         }
383     }
384 
385 
386     /**
387      * {@inheritDoc}
388      **/
389     @Override
390     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
391     {
392         // Check if the entry is a referral itself
393         boolean isReferral = isReferral( moveAndRenameContext.getOriginalEntry() );
394 
395         next( moveAndRenameContext );
396 
397         if ( isReferral )
398         {
399             // Update the referralManager
400             Entry newEntry = moveAndRenameContext.getModifiedEntry();
401 
402             referralManager.lockWrite();
403 
404             try
405             {
406                 referralManager.addReferral( newEntry );
407                 referralManager.removeReferral( moveAndRenameContext.getOriginalEntry() );
408             }
409             finally
410             {
411                 referralManager.unlock();
412             }
413         }
414     }
415 
416 
417     /**
418      * {@inheritDoc}
419      **/
420     @Override
421     public void rename( RenameOperationContext renameContext ) throws LdapException
422     {
423         // Check if the entry is a referral itself
424         boolean isReferral = isReferral( renameContext.getOriginalEntry() );
425 
426         next( renameContext );
427 
428         if ( isReferral )
429         {
430             // Update the referralManager
431             LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( renameContext.getSession(),
432                 renameContext.getNewDn(), SchemaConstants.ALL_ATTRIBUTES_ARRAY );
433             lookupContext.setPartition( renameContext.getPartition() );
434             lookupContext.setTransaction( renameContext.getTransaction() );
435 
436             Entry newEntry = nexus.lookup( lookupContext );
437 
438             referralManager.lockWrite();
439 
440             try
441             {
442                 referralManager.addReferral( newEntry );
443                 referralManager.removeReferral( ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry() );
444             }
445             finally
446             {
447                 referralManager.unlock();
448             }
449         }
450     }
451 }