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.mitosis.operation;
21  
22  
23  import org.apache.directory.mitosis.common.CSN;
24  import org.apache.directory.mitosis.common.CSNFactory;
25  import org.apache.directory.mitosis.common.Constants;
26  import org.apache.directory.mitosis.configuration.ReplicationConfiguration;
27  import org.apache.directory.server.constants.ServerDNConstants;
28  import org.apache.directory.server.core.CoreSession;
29  import org.apache.directory.server.core.DefaultCoreSession;
30  import org.apache.directory.server.core.DirectoryService;
31  import org.apache.directory.server.core.authn.LdapPrincipal;
32  import org.apache.directory.server.core.entry.DefaultServerAttribute;
33  import org.apache.directory.server.core.entry.ServerAttribute;
34  import org.apache.directory.server.core.entry.ServerEntry;
35  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
36  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
37  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
38  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
39  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
40  import org.apache.directory.server.core.partition.Partition;
41  import org.apache.directory.server.core.partition.PartitionNexus;
42  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
43  import org.apache.directory.server.schema.registries.Registries;
44  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
45  import org.apache.directory.shared.ldap.constants.SchemaConstants;
46  import org.apache.directory.shared.ldap.entry.EntryAttribute;
47  import org.apache.directory.shared.ldap.entry.Modification;
48  import org.apache.directory.shared.ldap.entry.ModificationOperation;
49  import org.apache.directory.shared.ldap.filter.PresenceNode;
50  import org.apache.directory.shared.ldap.message.AliasDerefMode;
51  import org.apache.directory.shared.ldap.name.LdapDN;
52  import org.apache.directory.shared.ldap.name.Rdn;
53  
54  import javax.naming.NameAlreadyBoundException;
55  import javax.naming.NamingException;
56  import javax.naming.directory.SearchControls;
57  
58  import java.util.List;
59  import java.util.UUID;
60  
61  
62  /**
63   * Creates an {@link Operation} instance for a JNDI operation.  The
64   * {@link Operation} instance returned by the provided factory methods are
65   * mostly a {@link CompositeOperation}, which consists smaller JNDI
66   * operations. The elements of the {@link CompositeOperation} differs from
67   * the original JNDI operation to make the operation more robust to
68   * replication conflict.  All {@link Operation}s created by
69   * {@link OperationFactory} whould be robust to the replication conflict and
70   * should be able to recover from the conflict.
71   * <p>
72   * "Add" (or "bind") is the only operation that doesn't return a
73   * {@link CompositeOperation} but returns an {@link AddEntryOperation}.
74   * It is because all other operations needs to update its related entry's
75   * {@link Constants#ENTRY_CSN} or {@link Constants#ENTRY_DELETED} attribute
76   * with additional sub-operations.  In contrast, "add" operation doesn't need
77   * to create a {@link CompositeOperation} because those attributes can be
78   * added just modifying an {@link AddEntryOperation} rather than creating
79   * a parent operation and add sub-operations there.
80   * <p>
81   * Please note that all operations update {@link Constants#ENTRY_CSN} and
82   * documentation for each method won't explain this behavior.
83   * 
84   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
85   */
86  public class OperationFactory
87  {
88      private final String replicaId;
89      private final PartitionNexus nexus;
90      private final CSNFactory csnFactory;
91      
92      /** The attributeType registry */
93      private final AttributeTypeRegistry attributeRegistry;
94  
95      /** The global registries */
96      private Registries registries;
97      
98      
99      private DirectoryService ds;
100 
101     
102     public OperationFactory( DirectoryService directoryService, ReplicationConfiguration cfg )
103     {
104         replicaId = cfg.getReplicaId();
105         nexus = directoryService.getPartitionNexus();
106         csnFactory = cfg.getCsnFactory();
107         registries = directoryService.getRegistries();
108         attributeRegistry = registries.getAttributeTypeRegistry();
109         this.ds = directoryService;
110     }
111 
112 
113     /**
114      * Creates a new {@link Operation} that performs LDAP "add" operation
115      * with a newly generated {@link CSN}.
116      */
117     public Operation newAdd( LdapDN normalizedName, ServerEntry entry ) throws Exception
118     {
119         return newAdd( newCSN(), normalizedName, entry );
120     }
121 
122 
123     /**
124      * Creates a new {@link Operation} that performs LDAP "add" operation
125      * with the specified {@link CSN}.  The new entry will have three
126      * additional attributes; {@link Constants#ENTRY_CSN} ({@link CSN}),
127      * {@link Constants#ENTRY_UUID}, and {@link Constants#ENTRY_DELETED}.
128      */
129     private Operation newAdd( CSN csn, LdapDN normalizedName, ServerEntry entry ) throws Exception
130     {
131         // Check an entry already exists.
132         checkBeforeAdd( normalizedName );
133 
134         // Insert 'entryUUID' and 'entryDeleted'.
135         ServerEntry cloneEntry = ( ServerEntry ) entry.clone();
136         cloneEntry.removeAttributes( Constants.ENTRY_UUID );
137         cloneEntry.removeAttributes( Constants.ENTRY_DELETED );
138         cloneEntry.put( Constants.ENTRY_UUID, UUID.randomUUID().toString() );
139         cloneEntry.put( Constants.ENTRY_DELETED, "FALSE" );
140 
141         // NOTE: We inlined addDefaultOperations() because ApacheDS currently
142         // creates an index entry only for ADD operation (and not for
143         // MODIFY operation)
144         cloneEntry.put( Constants.ENTRY_CSN, csn.toOctetString() );
145 
146         return new AddEntryOperation( registries, csn, cloneEntry );
147     }
148 
149 
150     /**
151      * Creates a new {@link Operation} that performs "delete" operation.
152      * The created {@link Operation} doesn't actually delete the entry.
153      * Instead, it sets {@link Constants#ENTRY_DELETED} to "TRUE". 
154      */
155     public Operation newDelete( LdapDN normalizedName ) throws NamingException
156     {
157         CSN csn = newCSN();
158         CompositeOperation result = new CompositeOperation( registries, csn );
159 
160         // Transform into replace operation.
161         result.add( new ReplaceAttributeOperation( registries, csn, normalizedName, 
162             new DefaultServerAttribute( 
163                 Constants.ENTRY_DELETED, 
164                 attributeRegistry.lookup( Constants.ENTRY_DELETED ),
165                 "TRUE" ) ) );
166 
167         return addDefaultOperations( result, csn, normalizedName );
168     }
169 
170 
171     /**
172      * Returns a new {@link Operation} that performs "modify" operation.
173      * 
174      * @return a {@link CompositeOperation} that consists of one or more
175      * {@link AttributeOperation}s and one additional operation that
176      * sets {@link Constants#ENTRY_DELETED} to "FALSE" to resurrect the
177      * entry the modified attributes belong to.
178      */
179     public Operation newModify( ModifyOperationContext opContext ) throws NamingException
180     {
181         List<Modification> items = opContext.getModItems();
182         LdapDN normalizedName = opContext.getDn();
183 
184         CSN csn = newCSN();
185         CompositeOperation result = new CompositeOperation( registries, csn );
186         
187         // Transform into multiple {@link AttributeOperation}s.
188         for ( Modification item:items )
189         {
190             result.add( 
191                 newModify( 
192                     csn, 
193                     normalizedName, 
194                     item.getOperation(), 
195                     (ServerAttribute)item.getAttribute() ) );
196         }
197 
198         // Resurrect the entry in case it is deleted.
199         result.add( 
200             new ReplaceAttributeOperation( 
201                 registries, 
202                 csn, 
203                 normalizedName, 
204                 new DefaultServerAttribute( 
205                     Constants.ENTRY_DELETED,
206                     attributeRegistry.lookup( Constants.ENTRY_DELETED ),
207                     "FALSE" ) ) );
208 
209         return addDefaultOperations( result, csn, normalizedName );
210     }
211 
212 
213     /**
214      * Returns a new {@link AttributeOperation} that performs one 
215      * attribute modification operation.  This method is called by other
216      * methods internally to create an appropriate {@link AttributeOperation}
217      * instance from the specified <tt>modOp</tt> value.
218      */
219     private Operation newModify( CSN csn, LdapDN normalizedName, ModificationOperation modOp, ServerAttribute attribute )
220     {
221         switch ( modOp )
222         {
223             case ADD_ATTRIBUTE:
224                 return new AddAttributeOperation( registries, csn, normalizedName, attribute );
225             
226             case REPLACE_ATTRIBUTE:
227                 return new ReplaceAttributeOperation( registries, csn, normalizedName, attribute );
228             
229             case REMOVE_ATTRIBUTE:
230                 return new DeleteAttributeOperation( registries, csn, normalizedName, attribute );
231             
232             default:
233                 throw new IllegalArgumentException( "Unknown modOp: " + modOp );
234         }
235     }
236 
237 
238     /**
239      * Returns a new {@link Operation} that performs "modifyRN" operation.
240      * This operation is a subset of "move" operation.
241      * Calling this method actually forwards the call to
242      * {@link #newMove(LdapDN, LdapDN, Rdn, boolean)} with unchanged
243      * <tt>newParentName</tt>. 
244      */
245     public Operation newModifyRn( LdapDN oldName, Rdn newRdn, boolean deleteOldRn ) throws Exception
246     {
247         LdapDN newParentName = ( LdapDN ) oldName.clone();
248         newParentName.remove( oldName.size() - 1 );
249         
250         return newMove( oldName, newParentName, newRdn, deleteOldRn );
251     }
252 
253 
254     /**
255      * Returns a new {@link Operation} that performs "move" operation.
256      * Calling this method actually forwards the call to
257      * {@link #newMove(LdapDN, LdapDN, Rdn, boolean)} with unchanged
258      * <tt>newRdn</tt> and '<tt>true</tt>' <tt>deleteOldRn</tt>. 
259      */
260     public Operation newMove( LdapDN oldName, LdapDN newParentName ) throws Exception
261     {
262         return newMove( oldName, newParentName, oldName.getRdn(), true );
263     }
264 
265 
266     /**
267      * Returns a new {@link Operation} that performs "move" operation.
268      * Please note this operation is the most fragile operation I've written
269      * so it should be reviewed completely again.
270      */
271     public Operation newMove( LdapDN oldName, LdapDN newParentName, Rdn newRdn, boolean deleteOldRn )
272         throws Exception
273     {
274         // Prepare to create composite operations
275         CSN csn = newCSN();
276         CompositeOperation result = new CompositeOperation( registries, csn );
277 
278         // Retrieve all subtree including the base entry
279         SearchControls ctrl = new SearchControls();
280         ctrl.setSearchScope( SearchControls.SUBTREE_SCOPE );
281         
282         LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
283         adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
284         CoreSession adminSession = new DefaultCoreSession( 
285             new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), ds );
286 
287         EntryFilteringCursor cursor = nexus.search( 
288             new SearchOperationContext( adminSession, oldName, AliasDerefMode.DEREF_ALWAYS,
289                     new PresenceNode( SchemaConstants.OBJECT_CLASS_AT_OID ), ctrl ) );
290 
291         while ( cursor.next() )
292         {
293             ServerEntry entry = cursor.get();
294 
295             // Get the name of the old entry
296             LdapDN oldEntryName = entry.getDn();
297             oldEntryName.normalize( attributeRegistry.getNormalizerMapping() );
298 
299             // Delete the old entry
300             result.add( 
301                 new ReplaceAttributeOperation( 
302                     registries, 
303                     csn, 
304                     oldEntryName, 
305                     new DefaultServerAttribute( 
306                         Constants.ENTRY_DELETED,
307                         attributeRegistry.lookup( Constants.ENTRY_DELETED ),
308                         "TRUE" ) ) );
309 
310             // Get the old entry attributes and replace RDN if required
311             if ( oldEntryName.size() == oldName.size() )
312             {
313                 if ( deleteOldRn )
314                 {
315                     // Delete the old RDN attribute value
316                     String oldRDNAttributeID = oldName.getRdn().getUpType();
317                     EntryAttribute oldRDNAttribute = entry.get( oldRDNAttributeID );
318                     
319                     if ( oldRDNAttribute != null )
320                     {
321                         boolean removed = oldRDNAttribute.remove( (String)oldName.getRdn().getUpValue() );
322                         
323                         if ( removed && oldRDNAttribute.size() == 0 )
324                         {
325                             // Now an empty attribute, remove it.
326                             entry.removeAttributes( oldRDNAttributeID );
327                         }
328                     }
329                 }
330                 
331                 // Add the new RDN attribute value.
332                 String newRDNAttributeID = newRdn.getUpType();
333                 String newRDNAttributeValue = ( String ) newRdn.getUpValue();
334                 EntryAttribute newRDNAttribute = entry.get( newRDNAttributeID );
335                 
336                 if ( newRDNAttribute != null )
337                 {
338                     newRDNAttribute.add( newRDNAttributeValue );
339                 }
340                 else
341                 {
342                     entry.put( newRDNAttributeID, newRDNAttributeValue );
343                 }
344             }
345 
346             // Calculate new name from newParentName, oldEntryName, and newRdn.
347             LdapDN newEntryName = ( LdapDN ) newParentName.clone();
348             newEntryName.add( newRdn );
349             
350             for ( int i = oldEntryName.size() - newEntryName.size(); i > 0; i-- )
351             {
352                 newEntryName.add( oldEntryName.get( oldEntryName.size() - i ) );
353             }
354             
355             newEntryName.normalize( attributeRegistry.getNormalizerMapping() );
356 
357             // Add the new entry
358             result.add( newAdd( csn, newEntryName, entry ) );
359 
360             // Add default operations to the old entry.
361             // Please note that newAdd() already added default operations
362             // to the new entry. 
363             addDefaultOperations( result, csn, oldEntryName );
364         }
365 
366         return result;
367     }
368 
369 
370     /**
371      * Make sure the specified <tt>newEntryName</tt> already exists.  It
372      * checked {@link Constants#ENTRY_DELETED} additionally to see if the
373      * entry actually exists in a {@link Partition} but maked as deleted.
374      *
375      * @param newEntryName makes sure an entry already exists.
376      */
377     private void checkBeforeAdd( LdapDN newEntryName ) throws Exception
378     {
379         LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
380         adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
381         CoreSession adminSession = new DefaultCoreSession( 
382             new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), ds );
383 
384         if ( nexus.hasEntry( new EntryOperationContext( adminSession, newEntryName ) ) )
385         {
386             ServerEntry entry = nexus.lookup( new LookupOperationContext( adminSession, newEntryName ) );
387             EntryAttribute deleted = entry.get( Constants.ENTRY_DELETED );
388             Object value = deleted == null ? null : deleted.get();
389 
390             /*
391              * Check first if the entry has been marked as deleted before
392              * throwing an exception and delete the entry if so and return
393              * without throwing an exception.
394              */
395             if ( value != null && "TRUE".equalsIgnoreCase( value.toString() ) )
396             {
397                 return;
398             }
399 
400             throw new NameAlreadyBoundException( newEntryName.toString() + " already exists." );
401         }
402     }
403 
404 
405     /**
406      * Adds default {@link Operation}s that should be followed by all
407      * JNDI/LDAP operations except "add/bind" operation.  This method
408      * currently adds only one attribute, {@link Constants#ENTRY_CSN}.
409      * @return what you specified as a parameter to enable invocation chaining
410      */
411     private CompositeOperation addDefaultOperations( CompositeOperation result, CSN csn, LdapDN normalizedName ) throws NamingException
412     {
413         result.add( 
414             new ReplaceAttributeOperation( 
415                 registries, 
416                 csn, 
417                 normalizedName, 
418                 new DefaultServerAttribute( 
419                     Constants.ENTRY_DELETED,
420                     attributeRegistry.lookup( Constants.ENTRY_CSN ),
421                     csn.toOctetString() ) ) );
422 
423         return result;
424     }
425 
426     /**
427      * Creates new {@link CSN} from the {@link CSNFactory} which was specified
428      * in the constructor.
429      */
430     private CSN newCSN()
431     {
432         return csnFactory.newInstance( replicaId );
433     }
434 }