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: 1052987 $ $Date: 2010-12-27 09:27:28 +0800 (Mon, 27 Dec 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 *
203 * @param superClassName The super class name should be in the format of org/test/SimpleClass
204 * @return return true if the method parameter superClassName is assignable to any interface in the expected interfaces or any class in the expected classes
205 */
206 private boolean isSuperClassAssignable(String superClassName) {
207 if (targetClassNames.contains(superClassName)) {
208 return true;
209 } else if (superClassName.equals("java/lang/Object")) {
210 return false;
211 }
212
213 //Check parent class
214 URL url = bundle.getResource(superClassName + ".class");
215 if (url == null) {
216 //TODO what should we do if we do not find the super class ?
217 return false;
218 }
219 InputStream in = null;
220 try {
221 in = url.openStream();
222 ClassReader classReader = new ClassReader(in);
223
224 //Check interfaces
225 String[] superInterfaceNames = classReader.getInterfaces();
226 for (String superInterfaceName : superInterfaceNames) {
227 if (isInterfaceAssignable(superInterfaceName)) {
228 return true;
229 }
230 }
231
232 //Check className
233 return isSuperClassAssignable(classReader.getSuperName());
234 } catch (IOException e) {
235 logger.warn("Unable to check the super class " + superClassName, e);
236 return false;
237 } finally {
238 if (in != null) {
239 try {
240 in.close();
241 } catch (Exception e) {
242 }
243 }
244 }
245 }
246
247 /**
248 * Get the ASM style package name from the parameter className.
249 * If the className is ended with .class extension, e.g. /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class,
250 * then org/apache/geronimo is returned
251 * If the className is not ended with .class extension, e.g. /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass,
252 * then org/apache/geronimo is returned
253 * @param className
254 * @return ASM style package name, should be in the format of "org/apache/geronimo"
255 */
256 protected String toASMStylePackageName(String className) {
257 if (className.endsWith(EXT)) {
258 className = className.substring(0, className.length() - EXT.length());
259 }
260 className = className.replace('.', '/');
261 int iLastDotIndex = className.lastIndexOf('/');
262 if (iLastDotIndex != -1) {
263 return className.substring(0, iLastDotIndex);
264 } else {
265 return "";
266 }
267 }
268 }