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.exception;
21  
22  
23  import org.apache.commons.collections4.map.LRUMap;
24  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
25  import org.apache.directory.api.ldap.model.entry.Attribute;
26  import org.apache.directory.api.ldap.model.entry.Entry;
27  import org.apache.directory.api.ldap.model.entry.Value;
28  import org.apache.directory.api.ldap.model.exception.LdapAliasException;
29  import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
30  import org.apache.directory.api.ldap.model.exception.LdapException;
31  import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
32  import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
33  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
34  import org.apache.directory.api.ldap.model.name.Dn;
35  import org.apache.directory.server.core.api.CoreSession;
36  import org.apache.directory.server.core.api.DirectoryService;
37  import org.apache.directory.server.core.api.InterceptorEnum;
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.Interceptor;
41  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
42  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
43  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
44  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
45  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
46  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
47  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
48  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
50  import org.apache.directory.server.core.api.partition.Partition;
51  import org.apache.directory.server.core.api.partition.PartitionNexus;
52  import org.apache.directory.server.i18n.I18n;
53  
54  
55  /**
56   * An {@link Interceptor} that detects any operations that breaks integrity
57   * of {@link Partition} and terminates the current invocation chain by
58   * throwing a {@link Exception}. Those operations include when an entry
59   * already exists at a Dn and is added once again to the same Dn.
60   *
61   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
62   */
63  public class ExceptionInterceptor extends BaseInterceptor
64  {
65      private PartitionNexus nexus;
66      private Dn subschemSubentryDn;
67  
68      /**
69       * A cache to store entries which are not aliases.
70       * It's a speedup, we will be able to avoid backend lookups.
71       *
72       * Note that the backend also use a cache mechanism, but for performance gain, it's good
73       * to manage a cache here. The main problem is that when a user modify the parent, we will
74       * have to update it at three different places :
75       * - in the backend,
76       * - in the partition cache,
77       * - in this cache.
78       *
79       * The update of the backend and partition cache is already correctly handled, so we will
80       * just have to offer an access to refresh the local cache. This should be done in
81       * delete, modify and move operations.
82       *
83       * We need to be sure that frequently used DNs are always in cache, and not discarded.
84       * We will use a LRU cache for this purpose.
85       */
86      private final LRUMap notAliasCache = new LRUMap( DEFAULT_CACHE_SIZE );
87  
88      /** Declare a default for this cache. 100 entries seems to be enough */
89      private static final int DEFAULT_CACHE_SIZE = 100;
90  
91  
92      /**
93       * Creates an interceptor that is also the exception handling service.
94       */
95      public ExceptionInterceptor()
96      {
97          super( InterceptorEnum.EXCEPTION_INTERCEPTOR );
98      }
99  
100 
101     /**
102      * {@inheritDoc}
103      */
104     @Override
105     public void init( DirectoryService directoryService ) throws LdapException
106     {
107         super.init( directoryService );
108         nexus = directoryService.getPartitionNexus();
109         Value attr = nexus.getRootDseValue( directoryService.getAtProvider().getSubschemaSubentry() );
110         subschemSubentryDn = dnFactory.create( attr.getString() );
111     }
112 
113 
114     /**
115      * In the pre-invocation state this interceptor method checks to see if the entry to be added already exists.  If it
116      * does an exception is raised.
117      */
118     @Override
119     public void add( AddOperationContext addContext ) throws LdapException
120     {
121         Dn name = addContext.getDn();
122 
123         if ( subschemSubentryDn.equals( name ) )
124         {
125             throw new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_249 ) );
126         }
127 
128         Dn suffix = nexus.getSuffixDn( name );
129 
130         // we're adding the suffix entry so just ignore stuff to mess with the parent
131         if ( suffix.equals( name ) )
132         {
133             next( addContext );
134             return;
135         }
136 
137         Dn parentDn = name.getParent();
138 
139         // check if we're trying to add to a parent that is an alias
140         boolean notAnAlias;
141 
142         synchronized ( notAliasCache )
143         {
144             notAnAlias = notAliasCache.containsKey( parentDn.getNormName() );
145         }
146 
147         if ( !notAnAlias )
148         {
149             // We don't know if the parent is an alias or not, so we will launch a
150             // lookup, and update the cache if it's not an alias
151             Entry attrs;
152 
153             try
154             {
155                 CoreSession session = addContext.getSession();
156                 LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn,
157                     SchemaConstants.ALL_ATTRIBUTES_ARRAY );
158                 lookupContext.setPartition( addContext.getPartition() );
159                 lookupContext.setTransaction( addContext.getTransaction() );
160 
161                 attrs = directoryService.getPartitionNexus().lookup( lookupContext );
162             }
163             catch ( Exception e )
164             {
165                 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_251_PARENT_NOT_FOUND, parentDn.getName() ) );
166             }
167 
168             Attribute objectClass = ( ( ClonedServerEntry ) attrs ).getOriginalEntry().get(
169                 directoryService.getAtProvider().getObjectClass() );
170 
171             if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
172             {
173                 String msg = I18n.err( I18n.ERR_252_ALIAS_WITH_CHILD_NOT_ALLOWED, name.getName(), parentDn.getName() );
174                 throw new LdapAliasException( msg );
175             }
176             else
177             {
178                 synchronized ( notAliasCache )
179                 {
180                     notAliasCache.put( parentDn.getNormName(), parentDn );
181                 }
182             }
183         }
184 
185         next( addContext );
186     }
187 
188 
189     /**
190      * Checks to make sure the entry being deleted exists, and has no children, otherwise throws the appropriate
191      * LdapException.
192      */
193     @Override
194     public void delete( DeleteOperationContext deleteContext ) throws LdapException
195     {
196         Dn dn = deleteContext.getDn();
197 
198         if ( dn.equals( subschemSubentryDn ) )
199         {
200             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_253,
201                 subschemSubentryDn ) );
202         }
203 
204         next( deleteContext );
205 
206         // Update the alias cache
207         synchronized ( notAliasCache )
208         {
209             if ( notAliasCache.containsKey( dn.getNormName() ) )
210             {
211                 notAliasCache.remove( dn.getNormName() );
212             }
213         }
214     }
215 
216 
217     /**
218      * {@inheritDoc}
219      */
220     @Override
221     public void modify( ModifyOperationContext modifyContext ) throws LdapException
222     {
223         // check if entry to modify exists
224         String msg = "Attempt to modify non-existant entry: ";
225 
226         // handle operations against the schema subentry in the schema service
227         // and never try to look it up in the nexus below
228         if ( modifyContext.getDn().equals( subschemSubentryDn ) )
229         {
230             next( modifyContext );
231             return;
232         }
233 
234         // Check that the entry we read at the beginning exists. If
235         // not, we will throw an exception here
236         assertHasEntry( modifyContext, msg );
237 
238         // Let's assume that the new modified entry may be an alias,
239         // but we don't want to check that now...
240         // We will simply remove the Dn from the NotAlias cache.
241         // It would be smarter to check the modified attributes, but
242         // it would also be more complex.
243         synchronized ( notAliasCache )
244         {
245             if ( notAliasCache.containsKey( modifyContext.getDn().getNormName() ) )
246             {
247                 notAliasCache.remove( modifyContext.getDn().getNormName() );
248             }
249         }
250 
251         next( modifyContext );
252     }
253 
254 
255     /**
256      * {@inheritDoc}
257      */
258     @Override
259     public void move( MoveOperationContext moveContext ) throws LdapException
260     {
261         Dn oriChildName = moveContext.getDn();
262 
263         if ( oriChildName.equals( subschemSubentryDn ) )
264         {
265             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_258,
266                 subschemSubentryDn, subschemSubentryDn ) );
267         }
268 
269         next( moveContext );
270 
271         // Remove the original entry from the NotAlias cache, if needed
272         synchronized ( notAliasCache )
273         {
274             if ( notAliasCache.containsKey( oriChildName.getNormName() ) )
275             {
276                 notAliasCache.remove( oriChildName.getNormName() );
277             }
278         }
279     }
280 
281 
282     /**
283      * {@inheritDoc}
284      */
285     @Override
286     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
287     {
288         Dn oldDn = moveAndRenameContext.getDn();
289 
290         // Don't allow M&R in the SSSE
291         if ( oldDn.getNormName().equals( subschemSubentryDn.getNormName() ) )
292         {
293             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_258,
294                 subschemSubentryDn, subschemSubentryDn ) );
295         }
296 
297         // Remove the original entry from the NotAlias cache, if needed
298         synchronized ( notAliasCache )
299         {
300             if ( notAliasCache.containsKey( oldDn.getNormName() ) )
301             {
302                 notAliasCache.remove( oldDn.getNormName() );
303             }
304         }
305 
306         next( moveAndRenameContext );
307     }
308 
309 
310     /**
311      * {@inheritDoc}
312      */
313     @Override
314     public void rename( RenameOperationContext renameContext ) throws LdapException
315     {
316         Dn dn = renameContext.getDn();
317 
318         if ( dn.equals( subschemSubentryDn ) )
319         {
320             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_255,
321                 subschemSubentryDn, subschemSubentryDn ) );
322         }
323 
324         // check to see if target entry exists
325         Dn newDn = renameContext.getNewDn();
326 
327         HasEntryOperationContextcontext/HasEntryOperationContext.html#HasEntryOperationContext">HasEntryOperationContext hasEntryContext = new HasEntryOperationContext( renameContext.getSession(), newDn );
328         hasEntryContext.setPartition( renameContext.getPartition() );
329         hasEntryContext.setTransaction( renameContext.getTransaction() );
330 
331         if ( nexus.hasEntry( hasEntryContext ) )
332         {
333             // Ok, the target entry already exists.
334             // If the target entry has the same name than the modified entry, it's a rename on itself,
335             // we want to allow this.
336             if ( !newDn.equals( dn ) )
337             {
338                 throw new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newDn.getName() ) );
339             }
340         }
341 
342         // Remove the previous entry from the notAnAlias cache
343         synchronized ( notAliasCache )
344         {
345             if ( notAliasCache.containsKey( dn.getNormName() ) )
346             {
347                 notAliasCache.remove( dn.getNormName() );
348             }
349         }
350 
351         next( renameContext );
352     }
353 
354 
355     /**
356      * Asserts that an entry is present and as a side effect if it is not, creates a LdapNoSuchObjectException, which is
357      * used to set the before exception on the invocation - eventually the exception is thrown.
358      *
359      * @param msg        the message to prefix to the distinguished name for explanation
360      * @throws Exception if the entry does not exist
361      * @param nextInterceptor the next interceptor in the chain
362      */
363     private void assertHasEntry( OperationContext opContext, String msg ) throws LdapException
364     {
365         Dn dn = opContext.getDn();
366 
367         if ( subschemSubentryDn.equals( dn ) )
368         {
369             return;
370         }
371 
372         if ( opContext.getEntry() == null )
373         {
374             LdapNoSuchObjectException e;
375 
376             if ( msg != null )
377             {
378                 e = new LdapNoSuchObjectException( msg + dn.getName() );
379             }
380             else
381             {
382                 e = new LdapNoSuchObjectException( dn.getName() );
383             }
384 
385             throw e;
386         }
387     }
388 
389     /**
390      * Asserts that an entry is present and as a side effect if it is not, creates a LdapNoSuchObjectException, which is
391      * used to set the before exception on the invocation - eventually the exception is thrown.
392      *
393      * @param msg        the message to prefix to the distinguished name for explanation
394      * @param dn         the distinguished name of the entry that is asserted
395      * @throws Exception if the entry does not exist
396      * @param nextInterceptor the next interceptor in the chain
397      *
398     private void assertHasEntry( OperationContext opContext, String msg, Dn dn ) throws LdapException
399     {
400         if ( subschemSubentryDn.equals( dn ) )
401         {
402             return;
403         }
404 
405         if ( !opContext.hasEntry( dn, ByPassConstants.HAS_ENTRY_BYPASS ) )
406         {
407             LdapNoSuchObjectException e;
408 
409             if ( msg != null )
410             {
411                 e = new LdapNoSuchObjectException( msg + dn.getName() );
412             }
413             else
414             {
415                 e = new LdapNoSuchObjectException( dn.getName() );
416             }
417 
418             throw e;
419         }
420     }*/
421 }