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 */
020package org.apache.directory.server.core.shared;
021
022
023import java.io.IOException;
024import java.util.Set;
025import java.util.concurrent.locks.ReentrantReadWriteLock;
026
027import javax.naming.directory.SearchControls;
028
029import org.apache.directory.api.ldap.model.constants.SchemaConstants;
030import org.apache.directory.api.ldap.model.entry.Entry;
031import org.apache.directory.api.ldap.model.entry.Value;
032import org.apache.directory.api.ldap.model.exception.LdapException;
033import org.apache.directory.api.ldap.model.exception.LdapOperationException;
034import org.apache.directory.api.ldap.model.exception.LdapOtherException;
035import org.apache.directory.api.ldap.model.filter.EqualityNode;
036import org.apache.directory.api.ldap.model.filter.ExprNode;
037import org.apache.directory.api.ldap.model.message.AliasDerefMode;
038import org.apache.directory.api.ldap.model.name.Dn;
039import org.apache.directory.api.ldap.model.schema.AttributeType;
040import org.apache.directory.api.ldap.util.tree.DnNode;
041import org.apache.directory.server.core.api.CoreSession;
042import org.apache.directory.server.core.api.DirectoryService;
043import org.apache.directory.server.core.api.ReferralManager;
044import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
045import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
046import org.apache.directory.server.core.api.partition.Partition;
047import org.apache.directory.server.core.api.partition.PartitionNexus;
048import org.apache.directory.server.core.api.partition.PartitionTxn;
049
050
051/**
052 * Implement a referral Manager, handling the requests from the LDAP protocol.
053 * <br>
054 * Referrals are stored in a tree, where leaves are the referrals. We are using
055 * the very same structure than for the partition manager.
056 *
057 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
058 */
059public class ReferralManagerImpl implements ReferralManager
060{
061    /** The referrals tree */
062    private DnNode<Entry> referrals;
063
064    /** A lock to guarantee the manager consistency */
065    private ReentrantReadWriteLock mutex = new ReentrantReadWriteLock();
066
067    /** A storage for the ObjectClass attributeType */
068    private AttributeType objectClassAT;
069
070
071    /**
072     *
073     * Creates a new instance of ReferralManagerImpl.
074     *
075     * @param directoryService The directory service
076     * @throws LdapException If we can't initialize the manager
077     */
078    public ReferralManagerImpl( DirectoryService directoryService ) throws LdapException
079    {
080        lockWrite();
081
082        try
083        {
084            referrals = new DnNode<>();
085            PartitionNexus nexus = directoryService.getPartitionNexus();
086    
087            Set<String> suffixes = nexus.listSuffixes();
088            objectClassAT = directoryService.getSchemaManager().getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
089    
090            init( directoryService, suffixes.toArray( new String[]
091                {} ) );
092        }
093        finally
094        {
095            unlock();
096        }
097    }
098
099
100    /**
101     * Get a read-lock on the referralManager.
102     * No read operation can be done on the referralManager if this
103     * method is not called before.
104     */
105    @Override
106    public void lockRead()
107    {
108        mutex.readLock().lock();
109    }
110
111
112    /**
113     * Get a write-lock on the referralManager.
114     * No write operation can be done on the referralManager if this
115     * method is not called before.
116     */
117    @Override
118    public void lockWrite()
119    {
120        mutex.writeLock().lock();
121    }
122
123
124    /**
125     * Release the read-write lock on the referralManager.
126     * This method must be called after having read or modified the
127     * ReferralManager
128     */
129    @Override
130    public void unlock()
131    {
132        if ( mutex.isWriteLockedByCurrentThread() )
133        {
134            mutex.writeLock().unlock();
135        }
136        else
137        {
138            mutex.readLock().unlock();
139        }
140    }
141
142
143    /**
144     * {@inheritDoc}
145     */
146    @Override
147    public void addReferral( Entry entry )
148    {
149        try
150        {
151            referrals.add( entry.getDn(), entry );
152        }
153        catch ( LdapException ne )
154        {
155            // Do nothing
156        }
157    }
158
159
160    /**
161     * {@inheritDoc}
162     */
163    @Override
164    public void init( DirectoryService directoryService, String... suffixes ) throws LdapException
165    {
166        ExprNode referralFilter = new EqualityNode<String>( objectClassAT,
167            new Value( objectClassAT, SchemaConstants.REFERRAL_OC ) );
168
169        // Lookup for each entry with the ObjectClass = Referral value
170        SearchControls searchControl = new SearchControls();
171        searchControl.setReturningObjFlag( false );
172        searchControl.setSearchScope( SearchControls.SUBTREE_SCOPE );
173
174        CoreSession adminSession = directoryService.getAdminSession();
175        PartitionNexus nexus = directoryService.getPartitionNexus();
176
177        for ( String suffix : suffixes )
178        {
179            // We will store each entry's Dn into the Referral tree
180            Dn suffixDn = directoryService.getDnFactory().create( suffix );
181
182            SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, suffixDn,
183                referralFilter, searchControl );
184            
185            Partition partition = nexus.getPartition( suffixDn );
186            
187            try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
188            {
189                searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
190                searchOperationContext.setTransaction( partitionTxn );
191                searchOperationContext.setPartition( partition );
192                EntryFilteringCursor cursor = nexus.search( searchOperationContext );
193    
194                try
195                {
196                    // Move to the first entry in the cursor
197                    cursor.beforeFirst();
198    
199                    while ( cursor.next() )
200                    {
201                        Entry entry = cursor.get();
202    
203                        // Lock the referralManager
204                        lockWrite();
205    
206                        try
207                        {
208                            // Add it at the right place
209                            addReferral( entry );
210                        }
211                        finally
212                        { 
213                            // Unlock the referralManager
214                            unlock();
215                        }
216                    }
217    
218                    cursor.close();
219                }
220                catch ( Exception e )
221                {
222                    throw new LdapOperationException( e.getMessage(), e );
223                }
224            }
225            catch ( IOException ioe )
226            {
227                throw new LdapOtherException( ioe.getMessage(), ioe );
228            }
229        }
230    }
231
232
233    /**
234     * {@inheritDoc}
235     */
236    @Override
237    public void remove( DirectoryService directoryService, Dn suffix ) throws Exception
238    {
239        ExprNode referralFilter = new EqualityNode<String>( objectClassAT,
240            new Value( objectClassAT, SchemaConstants.REFERRAL_OC ) );
241
242        // Lookup for each entry with the ObjectClass = Referral value
243        SearchControls searchControl = new SearchControls();
244        searchControl.setReturningObjFlag( false );
245        searchControl.setSearchScope( SearchControls.SUBTREE_SCOPE );
246
247        CoreSession adminSession = directoryService.getAdminSession();
248        PartitionNexus nexus = directoryService.getPartitionNexus();
249
250        // We will store each entry's Dn into the Referral tree
251        SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, suffix,
252            referralFilter, searchControl );
253        Partition partition = nexus.getPartition( suffix ); 
254        searchOperationContext.setPartition( partition );
255        searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
256        
257        try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
258        {
259            searchOperationContext.setTransaction( partitionTxn );
260            EntryFilteringCursor cursor = nexus.search( searchOperationContext );
261    
262            // Move to the first entry in the cursor
263            cursor.beforeFirst();
264    
265            while ( cursor.next() )
266            {
267                Entry entry = cursor.get();
268    
269                // Add it at the right place
270                removeReferral( entry );
271            }
272        } 
273    }
274
275
276    /**
277     * {@inheritDoc}
278     */
279    @Override
280    public boolean hasParentReferral( Dn dn )
281    {
282        DnNode<Entry> referral = referrals.getNode( dn );
283
284        return ( referral != null ) && referral.isLeaf();
285    }
286
287
288    /**
289     * {@inheritDoc}
290     */
291    @Override
292    public Entry getParentReferral( Dn dn )
293    {
294        if ( !hasParentReferral( dn ) )
295        {
296            return null;
297        }
298
299        return referrals.getElement( dn );
300    }
301
302
303    /**
304     * {@inheritDoc}
305     */
306    @Override
307    public boolean isReferral( Dn dn )
308    {
309        Entry parent = referrals.getElement( dn );
310
311        if ( parent != null )
312        {
313            return dn.equals( parent.getDn() );
314        }
315        else
316        {
317            return false;
318        }
319    }
320
321
322    /**
323     * {@inheritDoc}
324     */
325    @Override
326    public void removeReferral( Entry entry ) throws LdapException
327    {
328        referrals.remove( entry.getDn() );
329    }
330}