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