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 }