001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.xbean.finder; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.net.URL; 023 import java.util.HashSet; 024 import java.util.List; 025 import java.util.Set; 026 027 import org.apache.xbean.osgi.bundle.util.BundleClassFinder; 028 import org.apache.xbean.osgi.bundle.util.BundleDescription; 029 import org.apache.xbean.osgi.bundle.util.ClassDiscoveryFilter; 030 import org.objectweb.asm.ClassReader; 031 import org.objectweb.asm.Opcodes; 032 import org.osgi.framework.Bundle; 033 import org.osgi.service.packageadmin.ExportedPackage; 034 import org.osgi.service.packageadmin.PackageAdmin; 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 038 /** 039 * @version $Rev: 941557 $ $Date: 2010-05-06 03:16:59 +0200 (Thu, 06 May 2010) $ 040 */ 041 public class BundleAssignableClassFinder extends BundleClassFinder { 042 043 private static final Logger logger = LoggerFactory.getLogger(BundleAssignableClassFinder.class); 044 045 private Class<?>[] clses; 046 047 private Set<String> targetClassNames = new HashSet<String>(); 048 049 private Set<String> targetInterfaceNames = new HashSet<String>(); 050 051 private Set<String> wiredImportedPackageNames = new HashSet<String>(); 052 053 /** 054 * Create a new BundleClassFinder, it will search all the classes based the rule defined by the parameters via ASM tool 055 * @param packageAdmin 056 * @param bundle 057 * @param clses 058 * @param discoveryFilter 059 */ 060 public BundleAssignableClassFinder(PackageAdmin packageAdmin, Bundle bundle, Class<?>[] clses, ClassDiscoveryFilter discoveryFilter) { 061 super(packageAdmin, bundle, discoveryFilter); 062 if (clses == null || clses.length == 0) { 063 throw new IllegalArgumentException("At least one class or interface should be specified"); 064 } 065 this.clses = clses; 066 for (Class<?> cls : clses) { 067 String asmStyleName = cls.getName().replace('.', '/'); 068 if (cls.isInterface()) { 069 targetInterfaceNames.add(asmStyleName); 070 } else { 071 targetClassNames.add(asmStyleName); 072 } 073 } 074 initialize(); 075 } 076 077 public BundleAssignableClassFinder(PackageAdmin packageAdmin, Class<?>[] clses, Bundle bundle) { 078 this(packageAdmin, bundle, clses, FULL_CLASS_DISCOVERY_FILTER); 079 } 080 081 @Override 082 protected BundleClassFinder createSubBundleClassFinder(PackageAdmin packageAdmin, Bundle bundle, ClassDiscoveryFilter classDiscoveryFilter) { 083 return new BundleAssignableClassFinder(packageAdmin, bundle, clses, classDiscoveryFilter); 084 } 085 086 @Override 087 protected boolean isClassAcceptable(String name, InputStream in) throws IOException { 088 ClassReader classReader = new ClassReader(in); 089 String className = classReader.getClassName(); 090 if ((classReader.getAccess() & Opcodes.ACC_INTERFACE) == 0) { 091 if (targetClassNames.contains(className)) { 092 return true; 093 } 094 } else { 095 if (targetInterfaceNames.contains(className)) { 096 return true; 097 } 098 } 099 String[] interfaceNames = classReader.getInterfaces(); 100 try { 101 for (String interfaceName : interfaceNames) { 102 if (wiredImportedPackageNames.contains(toASMStylePackageName(interfaceName))) { 103 return isClassAssignable(bundle.loadClass(toJavaStyleClassName(interfaceName))); 104 } else { 105 if (isInterfaceAssignable(interfaceName)) { 106 return true; 107 } 108 } 109 } 110 String superClassName = classReader.getSuperName(); 111 if (wiredImportedPackageNames.contains(toASMStylePackageName(superClassName))) { 112 return isClassAssignable(bundle.loadClass(toJavaStyleClassName(superClassName))); 113 } 114 return isSuperClassAssignable(superClassName); 115 } catch (ClassNotFoundException e) { 116 return false; 117 } 118 } 119 120 @Override 121 protected boolean isClassAcceptable(URL url) { 122 InputStream in = null; 123 try { 124 in = url.openStream(); 125 return isClassAcceptable("", in); 126 } catch (IOException e) { 127 logger.warn("Unable to check the class of url " + url, e); 128 return false; 129 } finally { 130 if (in != null) 131 try { 132 in.close(); 133 } catch (Exception e) { 134 } 135 } 136 } 137 138 private void initialize() { 139 BundleDescription description = new BundleDescription(bundle.getHeaders()); 140 List<BundleDescription.ImportPackage> imports = description.getExternalImports(); 141 for (BundleDescription.ImportPackage packageImport : imports) { 142 String packageName = packageImport.getName(); 143 ExportedPackage[] exports = packageAdmin.getExportedPackages(packageName); 144 Bundle wiredBundle = isWired(bundle, exports); 145 if (wiredBundle != null) { 146 wiredImportedPackageNames.add(packageName.replace('.', '/')); 147 break; 148 } 149 } 150 } 151 152 private boolean isClassAssignable(Class<?> cls) { 153 for (Class<?> targetClass : clses) { 154 if (targetClass.isAssignableFrom(cls)) { 155 return true; 156 } 157 } 158 return false; 159 } 160 161 /** 162 * 163 * @param interfaceName The interface name should be in the format of org/test/SimpleInterface 164 * @return return true if the method parameter interfaceName is assignable to any interface in the expected interfaces 165 */ 166 private boolean isInterfaceAssignable(String interfaceName) { 167 //Check each interface in interfaceNames set 168 if (targetInterfaceNames.contains(interfaceName)) { 169 return true; 170 } 171 //Check ancestor intefaces 172 URL url = bundle.getResource("/" + interfaceName + ".class"); 173 if (url == null) { 174 //TODO what should we do if we do not find the interface ? 175 return false; 176 } 177 InputStream in = null; 178 try { 179 in = url.openStream(); 180 ClassReader classReader = new ClassReader(in); 181 String[] superInterfaceNames = classReader.getInterfaces(); 182 for (String superInterfaceName : superInterfaceNames) { 183 if (isInterfaceAssignable(superInterfaceName)) { 184 return true; 185 } 186 } 187 return false; 188 } catch (IOException e) { 189 logger.warn("Unable to check the interface " + interfaceName, e); 190 return false; 191 } finally { 192 if (in != null) 193 try { 194 in.close(); 195 } catch (Exception e) { 196 } 197 } 198 } 199 200 /** 201 * 202 * @param superClassName The super class name should be in the format of org/test/SimpleClass 203 * @return return true if the method parameter superClassName is assignable to any interface in the expected interfaces or any class in the expected classes 204 */ 205 private boolean isSuperClassAssignable(String superClassName) { 206 if (targetClassNames.contains(superClassName)) { 207 return true; 208 } 209 //Check parent class 210 URL url = bundle.getResource("/" + superClassName + ".class"); 211 if (url == null) { 212 //TODO what should we do if we do not find the super class ? 213 return false; 214 } 215 InputStream in = null; 216 try { 217 in = url.openStream(); 218 ClassReader classReader = new ClassReader(in); 219 String[] superInterfaceNames = classReader.getInterfaces(); 220 //Check interfaces 221 for (String superInterfaceName : superInterfaceNames) { 222 if (isInterfaceAssignable(superInterfaceName)) { 223 return true; 224 } 225 } 226 //Check className 227 if (classReader.getSuperName().equals("java/lang/Object")) { 228 return targetClassNames.contains("java/lang/Object"); 229 } else { 230 return isSuperClassAssignable(classReader.getSuperName()); 231 } 232 } catch (IOException e) { 233 logger.warn("Unable to check the super class " + superClassName, e); 234 return false; 235 } finally { 236 if (in != null) 237 try { 238 in.close(); 239 } catch (Exception e) { 240 } 241 } 242 } 243 244 /** 245 * Get the ASM style package name from the parameter className. 246 * If the className is ended with .class extension, e.g. /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class, 247 * then org/apache/geronimo is returned 248 * If the className is not ended with .class extension, e.g. /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass, 249 * then org/apache/geronimo is returned 250 * @param className 251 * @return ASM style package name, should be in the format of "org/apache/geronimo" 252 */ 253 protected String toASMStylePackageName(String className) { 254 if (className.endsWith(EXT)) { 255 className = className.substring(0, className.length() - EXT.length()); 256 } 257 className = className.replace('.', '/'); 258 int iLastDotIndex = className.lastIndexOf('/'); 259 if (iLastDotIndex != -1) { 260 return className.substring(0, iLastDotIndex); 261 } else { 262 return ""; 263 } 264 } 265 }