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}