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.sp;
021    
022    
023    import java.util.ArrayList;
024    import java.util.List;
025    import java.util.Set;
026    
027    
028    import org.apache.directory.server.constants.ApacheSchemaConstants;
029    import org.apache.directory.server.core.DirectoryService;
030    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
031    import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
032    import org.apache.directory.server.i18n.I18n;
033    import org.apache.directory.shared.ldap.constants.SchemaConstants;
034    import org.apache.directory.shared.ldap.entry.StringValue;
035    import org.apache.directory.shared.ldap.entry.EntryAttribute;
036    import org.apache.directory.shared.ldap.entry.ServerEntry;
037    import org.apache.directory.shared.ldap.entry.Value;
038    import org.apache.directory.shared.ldap.exception.LdapException;
039    import org.apache.directory.shared.ldap.filter.AndNode;
040    import org.apache.directory.shared.ldap.filter.BranchNode;
041    import org.apache.directory.shared.ldap.filter.EqualityNode;
042    import org.apache.directory.shared.ldap.filter.SearchScope;
043    import org.apache.directory.shared.ldap.message.AliasDerefMode;
044    import org.apache.directory.shared.ldap.name.DN;
045    import org.slf4j.Logger;
046    import org.slf4j.LoggerFactory;
047    
048    
049    /**
050     * A class loader that loads classes from an LDAP DIT.
051     * 
052     * <p>
053     * This loader looks for an configuration entry whose DN is
054     * determined by defaultSearchContextsConfig variable. If there is such
055     * an entry it gets the search contexts from the entry and searches the 
056     * class to be loaded in those contexts.
057     * If there is no default search context configuration entry it searches
058     * the class in the whole DIT. 
059     * 
060     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
061     * @version $Rev$ $Date$
062     */
063    public class LdapClassLoader extends ClassLoader
064    {
065        private static final Logger log = LoggerFactory.getLogger( LdapClassLoader.class );
066        public static String defaultSearchContextsConfig = "cn=classLoaderDefaultSearchContext,ou=configuration,ou=system";
067        
068        private DN defaultSearchDn;
069        private DirectoryService directoryService;
070    
071        
072        public LdapClassLoader( DirectoryService directoryService ) throws LdapException
073        {
074            super( LdapClassLoader.class.getClassLoader() );
075            this.directoryService = directoryService;
076            defaultSearchDn = new DN( defaultSearchContextsConfig );
077            defaultSearchDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
078        }
079    
080        
081        private byte[] findClassInDIT( List<DN> searchContexts, String name ) throws ClassNotFoundException
082        {
083            // Set up the search filter
084            BranchNode filter = new AndNode( );
085            filter.addNode( new EqualityNode<String>( "fullyQualifiedJavaClassName", 
086                new StringValue( name ) ) );
087            filter.addNode( new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, 
088                new StringValue( ApacheSchemaConstants.JAVA_CLASS_OC ) ) );
089            
090            try
091            {
092                for ( DN base : searchContexts )
093                {
094                    EntryFilteringCursor cursor = null;
095                    try
096                    {
097                        cursor = directoryService.getAdminSession()
098                            .search( base, SearchScope.SUBTREE, filter, AliasDerefMode.DEREF_ALWAYS, null );
099                        
100                        cursor.beforeFirst();
101                        if ( cursor.next() ) // there should be only one!
102                        {
103                            log.debug( "Class {} found under {} search context.", name, base );
104                            ServerEntry classEntry = cursor.get();
105    
106                            if ( cursor.next() )
107                            {
108                                ServerEntry other = cursor.get();
109                                log.warn( "More than one class found on classpath at locations: {} \n\tand {}", 
110                                    classEntry, other );
111                            }
112    
113                            return classEntry.get( "javaClassByteCode" ).getBytes();
114                        }
115                    }
116                    finally
117                    {
118                        if ( cursor != null )
119                        {
120                            cursor.close();
121                        }
122                    }
123                }
124            }
125            catch ( Exception e )
126            {
127                log.error( I18n.err( I18n.ERR_69, name ), e );
128            }
129    
130            throw new ClassNotFoundException();
131        }
132        
133        
134        public Class<?> findClass( String name ) throws ClassNotFoundException
135        {
136            byte[] classBytes = null;
137    
138            try 
139            {   
140                // TODO we should cache this information and register with the event
141                // service to get notified if this changes so we can update the cached
142                // copy - there's absolutely no reason why we should be performing this
143                // lookup every time!!!
144                
145                ServerEntry configEntry = null;
146                
147                try
148                {
149                    configEntry = directoryService.getAdminSession().lookup( defaultSearchDn );
150                }
151                catch ( LdapException e )
152                {
153                    log.debug( "No configuration data found for class loader default search contexts." );
154                }
155                
156                if ( configEntry != null )
157                {
158                    List<DN> searchContexts = new ArrayList<DN>();
159                    EntryAttribute attr = configEntry.get( "classLoaderDefaultSearchContext" );
160                    
161                    for ( Value<?> val : attr )
162                    {
163                        DN dn = new DN( val.getString() );
164                        dn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
165                        searchContexts.add( dn );
166                    }
167                    
168                    try
169                    {
170                        classBytes = findClassInDIT( searchContexts, name );
171                        
172                        log.debug( "Class " + name + " found under default search contexts." );
173                    }
174                    catch ( ClassNotFoundException e )
175                    {
176                        log.debug( "Class " + name + " could not be found under default search contexts." );
177                    }
178                }
179                
180                if ( classBytes == null )
181                {
182                    List<DN> namingContexts = new ArrayList<DN>();
183                    
184                    // TODO - why is this an operation????  Why can't we just list these damn things
185                    // who went stupid crazy making everything into a damn operation  !!!! grrrr 
186                    Set<String> suffixes = 
187                        directoryService.getPartitionNexus().listSuffixes( 
188                            new ListSuffixOperationContext( directoryService.getAdminSession() ) );
189    
190                    for ( String suffix:suffixes )
191                    {
192                        DN dn = new DN( suffix );
193                        dn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
194                        namingContexts.add( dn );
195                    }
196                    
197                    classBytes = findClassInDIT( namingContexts, name );
198                }
199            } 
200            catch ( ClassNotFoundException e )
201            {
202                String msg = I18n.err( I18n.ERR_293, name );
203                log.debug( msg );
204                throw new ClassNotFoundException( msg );
205            }
206            catch ( Exception e ) 
207            {
208                String msg = I18n.err( I18n.ERR_70, name );
209                log.error( msg, e );
210                throw new ClassNotFoundException( msg );
211            }
212            
213            return defineClass( name, classBytes, 0, classBytes.length );
214        }
215    }