| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.framework; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLStreamHandler; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.security.ProtectionDomain; |
| import java.security.SecureClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.felix.framework.Felix.FelixResolver; |
| import org.apache.felix.framework.cache.JarContent; |
| import org.apache.felix.framework.capabilityset.Attribute; |
| import org.apache.felix.framework.capabilityset.Capability; |
| import org.apache.felix.framework.capabilityset.Directive; |
| import org.apache.felix.framework.capabilityset.Requirement; |
| import org.apache.felix.framework.capabilityset.SimpleFilter; |
| import org.apache.felix.framework.resolver.Content; |
| import org.apache.felix.framework.resolver.Module; |
| import org.apache.felix.framework.resolver.ResolveException; |
| import org.apache.felix.framework.resolver.ResourceNotFoundException; |
| import org.apache.felix.framework.resolver.Wire; |
| import org.apache.felix.framework.resolver.WireImpl; |
| import org.apache.felix.framework.resolver.WireModuleImpl; |
| import org.apache.felix.framework.util.CompoundEnumeration; |
| import org.apache.felix.framework.util.FelixConstants; |
| import org.apache.felix.framework.util.IteratorToEnumeration; |
| import org.apache.felix.framework.util.SecureAction; |
| import org.apache.felix.framework.util.SecurityManagerEx; |
| import org.apache.felix.framework.util.Util; |
| import org.apache.felix.framework.util.manifestparser.CapabilityImpl; |
| import org.apache.felix.framework.util.manifestparser.ManifestParser; |
| import org.apache.felix.framework.util.manifestparser.R4Library; |
| import org.apache.felix.framework.util.manifestparser.RequirementImpl; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.BundleReference; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| |
| public class ModuleImpl implements Module |
| { |
| private final Logger m_logger; |
| private final Map m_configMap; |
| private final FelixResolver m_resolver; |
| private final String m_id; |
| private final Content m_content; |
| private final Map m_headerMap; |
| private final URLStreamHandler m_streamHandler; |
| |
| private final String m_manifestVersion; |
| private final boolean m_isExtension; |
| private final String m_symbolicName; |
| private final Version m_version; |
| |
| private final List<Capability> m_capabilities; |
| private List<Capability> m_cachedCapabilities = null; |
| private final List<Requirement> m_requirements; |
| private List<Requirement> m_cachedRequirements = null; |
| private final List<Requirement> m_dynamicRequirements; |
| private List<Requirement> m_cachedDynamicRequirements = null; |
| private final List<R4Library> m_nativeLibraries; |
| private final int m_declaredActivationPolicy; |
| private final List<String> m_activationIncludes; |
| private final List<String> m_activationExcludes; |
| |
| private final Bundle m_bundle; |
| |
| private List<Module> m_fragments = null; |
| private List<Wire> m_wires = null; |
| private List<Module> m_dependentHosts = new ArrayList<Module>(0); |
| private List<Module> m_dependentImporters = new ArrayList<Module>(0); |
| private List<Module> m_dependentRequirers = new ArrayList<Module>(0); |
| private volatile boolean m_isResolved = false; |
| |
| private Content[] m_contentPath; |
| private Content[] m_fragmentContents = null; |
| private ModuleClassLoader m_classLoader; |
| private boolean m_isActivationTriggered = false; |
| private ProtectionDomain m_protectionDomain = null; |
| private static SecureAction m_secureAction = new SecureAction(); |
| |
| // Bundle-specific class loader for boot delegation. |
| private final ClassLoader m_bootClassLoader; |
| // Default class loader for boot delegation. |
| private final static ClassLoader m_defBootClassLoader; |
| |
| // Statically define the default class loader for boot delegation. |
| static |
| { |
| ClassLoader cl = null; |
| try |
| { |
| Constructor ctor = m_secureAction.getDeclaredConstructor( |
| SecureClassLoader.class, new Class[] { ClassLoader.class }); |
| m_secureAction.setAccesssible(ctor); |
| cl = (ClassLoader) m_secureAction.invoke(ctor, new Object[] { null }); |
| } |
| catch (Throwable ex) |
| { |
| // On Android we get an exception if we set the parent class loader |
| // to null, so we will work around that case by setting the parent |
| // class loader to the system class loader in getClassLoader() below. |
| cl = null; |
| System.err.println("Problem creating boot delegation class loader: " + ex); |
| } |
| m_defBootClassLoader = cl; |
| } |
| |
| // Boot delegation packages. |
| private final String[] m_bootPkgs; |
| private final boolean[] m_bootPkgWildcards; |
| |
| // Boolean flag to enable/disable implicit boot delegation. |
| private final boolean m_implicitBootDelegation; |
| |
| // Re-usable security manager for accessing class context. |
| private static SecurityManagerEx m_sm = new SecurityManagerEx(); |
| |
| // Thread local to detect class loading cycles. |
| private final ThreadLocal m_cycleCheck = new ThreadLocal(); |
| |
| // Thread local to keep track of deferred activation. |
| private static final ThreadLocal m_deferredActivation = new ThreadLocal(); |
| |
| // Flag indicating whether we are on an old JVM or not. |
| private volatile static boolean m_isPreJava5 = false; |
| |
| static ThreadLocal<Boolean> m_jarUrl = new ThreadLocal<Boolean>() { |
| @Override |
| protected Boolean initialValue() { |
| return false; |
| } |
| }; |
| |
| /** |
| * This constructor is used by the extension manager, since it needs |
| * a constructor that does not throw an exception. |
| * @param logger |
| * @param bundle |
| * @param id |
| * @param bootPkgs |
| * @param bootPkgWildcards |
| * @throws org.osgi.framework.BundleException |
| */ |
| public ModuleImpl( |
| Logger logger, Map configMap, Bundle bundle, String id, |
| String[] bootPkgs, boolean[] bootPkgWildcards) |
| { |
| m_logger = logger; |
| m_configMap = configMap; |
| m_resolver = null; |
| m_bundle = bundle; |
| m_id = id; |
| m_headerMap = null; |
| m_content = null; |
| m_streamHandler = null; |
| m_bootPkgs = bootPkgs; |
| m_bootPkgWildcards = bootPkgWildcards; |
| m_manifestVersion = null; |
| m_symbolicName = null; |
| m_isExtension = false; |
| m_version = null; |
| m_capabilities = null; |
| m_requirements = null; |
| m_dynamicRequirements = null; |
| m_nativeLibraries = null; |
| m_declaredActivationPolicy = EAGER_ACTIVATION; |
| m_activationExcludes = null; |
| m_activationIncludes = null; |
| m_implicitBootDelegation = false; |
| m_bootClassLoader = m_defBootClassLoader; |
| } |
| |
| public ModuleImpl( |
| Logger logger, Map configMap, FelixResolver resolver, |
| Bundle bundle, String id, Map headerMap, Content content, |
| URLStreamHandler streamHandler, String[] bootPkgs, |
| boolean[] bootPkgWildcards) |
| throws BundleException |
| { |
| m_logger = logger; |
| m_configMap = configMap; |
| m_resolver = resolver; |
| m_bundle = bundle; |
| m_id = id; |
| m_headerMap = headerMap; |
| m_content = content; |
| m_streamHandler = streamHandler; |
| m_bootPkgs = bootPkgs; |
| m_bootPkgWildcards = bootPkgWildcards; |
| |
| m_implicitBootDelegation = |
| (m_configMap.get(FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP) == null) |
| || Boolean.valueOf( |
| (String) m_configMap.get( |
| FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP)).booleanValue(); |
| |
| ClassLoader bootLoader = m_defBootClassLoader; |
| Object map = m_configMap.get(FelixConstants.BOOT_CLASSLOADERS_PROP); |
| if (map instanceof Map) |
| { |
| Object l = ((Map) map).get(bundle); |
| if (l instanceof ClassLoader) |
| { |
| bootLoader = (ClassLoader) l; |
| } |
| } |
| m_bootClassLoader = bootLoader; |
| |
| ManifestParser mp = new ManifestParser(m_logger, m_configMap, this, m_headerMap); |
| |
| // Record some of the parsed metadata. Note, if this is an extension |
| // bundle it's exports are removed, since they will be added to the |
| // system bundle directly later on. |
| m_manifestVersion = mp.getManifestVersion(); |
| m_version = mp.getBundleVersion(); |
| m_capabilities = mp.isExtension() ? null : mp.getCapabilities(); |
| m_requirements = mp.getRequirements(); |
| m_dynamicRequirements = mp.getDynamicRequirements(); |
| m_nativeLibraries = mp.getLibraries(); |
| m_declaredActivationPolicy = mp.getActivationPolicy(); |
| m_activationExcludes = (mp.getActivationExcludeDirective() == null) |
| ? null |
| : ManifestParser.parseDelimitedString(mp.getActivationExcludeDirective(), ","); |
| m_activationIncludes = (mp.getActivationIncludeDirective() == null) |
| ? null |
| : ManifestParser.parseDelimitedString(mp.getActivationIncludeDirective(), ","); |
| m_symbolicName = mp.getSymbolicName(); |
| m_isExtension = mp.isExtension(); |
| } |
| |
| // |
| // Metadata access methods. |
| // |
| |
| public Map getHeaders() |
| { |
| return m_headerMap; |
| } |
| |
| public boolean isExtension() |
| { |
| return m_isExtension; |
| } |
| |
| public String getSymbolicName() |
| { |
| return m_symbolicName; |
| } |
| |
| public String getManifestVersion() |
| { |
| return m_manifestVersion; |
| } |
| |
| public Version getVersion() |
| { |
| return m_version; |
| } |
| |
| public synchronized List<Capability> getCapabilities() |
| { |
| if (m_cachedCapabilities == null) |
| { |
| List capList = (m_capabilities == null) |
| ? new ArrayList<Capability>() |
| : new ArrayList<Capability>(m_capabilities); |
| for (int fragIdx = 0; |
| (m_fragments != null) && (fragIdx < m_fragments.size()); |
| fragIdx++) |
| { |
| List<Capability> caps = m_fragments.get(fragIdx).getCapabilities(); |
| for (int capIdx = 0; |
| (caps != null) && (capIdx < caps.size()); |
| capIdx++) |
| { |
| if (caps.get(capIdx).getNamespace().equals(Capability.PACKAGE_NAMESPACE)) |
| { |
| capList.add( |
| new CapabilityImpl( |
| this, |
| caps.get(capIdx).getNamespace(), |
| caps.get(capIdx).getDirectives(), |
| caps.get(capIdx).getAttributes())); |
| } |
| } |
| } |
| m_cachedCapabilities = Collections.unmodifiableList(capList); |
| } |
| return m_cachedCapabilities; |
| } |
| |
| public synchronized List<Requirement> getRequirements() |
| { |
| if (m_cachedRequirements == null) |
| { |
| List<Requirement> reqList = (m_requirements == null) |
| ? new ArrayList() : new ArrayList(m_requirements); |
| for (int fragIdx = 0; |
| (m_fragments != null) && (fragIdx < m_fragments.size()); |
| fragIdx++) |
| { |
| List<Requirement> reqs = m_fragments.get(fragIdx).getRequirements(); |
| for (int reqIdx = 0; |
| (reqs != null) && (reqIdx < reqs.size()); |
| reqIdx++) |
| { |
| if (reqs.get(reqIdx).getNamespace().equals(Capability.PACKAGE_NAMESPACE) |
| || reqs.get(reqIdx).getNamespace().equals(Capability.MODULE_NAMESPACE)) |
| { |
| reqList.add( |
| new FragmentRequirement( |
| this, reqs.get(reqIdx))); |
| } |
| } |
| } |
| m_cachedRequirements = Collections.unmodifiableList(reqList); |
| } |
| return m_cachedRequirements; |
| } |
| |
| public synchronized List<Requirement> getDynamicRequirements() |
| { |
| if (m_cachedDynamicRequirements == null) |
| { |
| List<Requirement> reqList = (m_dynamicRequirements == null) |
| ? new ArrayList() : new ArrayList(m_dynamicRequirements); |
| for (int fragIdx = 0; |
| (m_fragments != null) && (fragIdx < m_fragments.size()); |
| fragIdx++) |
| { |
| List<Requirement> reqs = m_fragments.get(fragIdx).getDynamicRequirements(); |
| for (int reqIdx = 0; |
| (reqs != null) && (reqIdx < reqs.size()); |
| reqIdx++) |
| { |
| if (reqs.get(reqIdx).getNamespace().equals(Capability.PACKAGE_NAMESPACE)) |
| { |
| reqList.add(reqs.get(reqIdx)); |
| } |
| } |
| } |
| m_cachedDynamicRequirements = Collections.unmodifiableList(reqList); |
| } |
| return m_cachedDynamicRequirements; |
| } |
| |
| public synchronized List<R4Library> getNativeLibraries() |
| { |
| List<R4Library> result = null; |
| if (m_isResolved) |
| { |
| List<R4Library> nativeList = (m_nativeLibraries == null) |
| ? new ArrayList() : new ArrayList(m_nativeLibraries); |
| for (int fragIdx = 0; |
| (m_fragments != null) && (fragIdx < m_fragments.size()); |
| fragIdx++) |
| { |
| List<R4Library> libs = m_fragments.get(fragIdx).getNativeLibraries(); |
| for (int reqIdx = 0; |
| (libs != null) && (reqIdx < libs.size()); |
| reqIdx++) |
| { |
| nativeList.add(libs.get(reqIdx)); |
| } |
| } |
| |
| // We need to return null here if we don't have any libraries, since a |
| // zero-length array is used to indicate that matching native libraries |
| // could not be found when resolving the bundle. |
| result = (nativeList.size() == 0) |
| ? null |
| : Collections.unmodifiableList(nativeList); |
| } |
| else |
| { |
| result = m_nativeLibraries; |
| } |
| |
| return result; |
| } |
| |
| public int getDeclaredActivationPolicy() |
| { |
| return m_declaredActivationPolicy; |
| } |
| |
| synchronized boolean isActivationTriggered() |
| { |
| return m_isActivationTriggered; |
| } |
| |
| boolean isActivationTrigger(String pkgName) |
| { |
| if ((m_activationIncludes == null) && (m_activationExcludes == null)) |
| { |
| return true; |
| } |
| |
| // If there are no include filters then all classes are included |
| // by default, otherwise try to find one match. |
| boolean included = (m_activationIncludes == null); |
| for (int i = 0; |
| (!included) && (m_activationIncludes != null) && (i < m_activationIncludes.size()); |
| i++) |
| { |
| included = m_activationIncludes.get(i).equals(pkgName); |
| } |
| |
| // If there are no exclude filters then no classes are excluded |
| // by default, otherwise try to find one match. |
| boolean excluded = false; |
| for (int i = 0; |
| (!excluded) && (m_activationExcludes != null) && (i < m_activationExcludes.size()); |
| i++) |
| { |
| excluded = m_activationExcludes.get(i).equals(pkgName); |
| } |
| return included && !excluded; |
| } |
| |
| // |
| // Run-time data access. |
| // |
| |
| public Bundle getBundle() |
| { |
| return m_bundle; |
| } |
| |
| public String getId() |
| { |
| return m_id; |
| } |
| |
| public synchronized List<Wire> getWires() |
| { |
| return m_wires; |
| } |
| |
| public synchronized void setWires(List<Wire> wires) |
| { |
| // Remove module from old wire modules' dependencies, |
| // since we are no longer dependent on any the moduels |
| // from the old wires. |
| for (int i = 0; (m_wires != null) && (i < m_wires.size()); i++) |
| { |
| if (m_wires.get(i).getCapability().getNamespace().equals(Capability.MODULE_NAMESPACE)) |
| { |
| ((ModuleImpl) m_wires.get(i).getExporter()).removeDependentRequirer(this); |
| } |
| else if (m_wires.get(i).getCapability().getNamespace().equals(Capability.PACKAGE_NAMESPACE)) |
| { |
| ((ModuleImpl) m_wires.get(i).getExporter()).removeDependentImporter(this); |
| } |
| } |
| |
| m_wires = wires; |
| |
| // Add ourself as a dependent to the new wires' modules. |
| for (int i = 0; (m_wires != null) && (i < m_wires.size()); i++) |
| { |
| if (m_wires.get(i).getCapability().getNamespace().equals(Capability.MODULE_NAMESPACE)) |
| { |
| ((ModuleImpl) m_wires.get(i).getExporter()).addDependentRequirer(this); |
| } |
| else if (m_wires.get(i).getCapability().getNamespace().equals(Capability.PACKAGE_NAMESPACE)) |
| { |
| ((ModuleImpl) m_wires.get(i).getExporter()).addDependentImporter(this); |
| } |
| } |
| } |
| |
| public boolean isResolved() |
| { |
| return m_isResolved; |
| } |
| |
| public void setResolved() |
| { |
| m_isResolved = true; |
| } |
| |
| // |
| // Content access methods. |
| // |
| |
| public Content getContent() |
| { |
| return m_content; |
| } |
| |
| private synchronized Content[] getContentPath() |
| { |
| if (m_contentPath == null) |
| { |
| try |
| { |
| m_contentPath = initializeContentPath(); |
| } |
| catch (Exception ex) |
| { |
| m_logger.log( |
| m_bundle, Logger.LOG_ERROR, "Unable to get module class path.", ex); |
| } |
| } |
| return m_contentPath; |
| } |
| |
| private Content[] initializeContentPath() throws Exception |
| { |
| List contentList = new ArrayList(); |
| calculateContentPath(this, m_content, contentList, true); |
| for (int i = 0; (m_fragmentContents != null) && (i < m_fragmentContents.length); i++) |
| { |
| calculateContentPath(m_fragments.get(i), m_fragmentContents[i], contentList, false); |
| } |
| return (Content[]) contentList.toArray(new Content[contentList.size()]); |
| } |
| |
| private List calculateContentPath( |
| Module module, Content content, List contentList, boolean searchFragments) |
| throws Exception |
| { |
| // Creating the content path entails examining the bundle's |
| // class path to determine whether the bundle JAR file itself |
| // is on the bundle's class path and then creating content |
| // objects for everything on the class path. |
| |
| // Create a list to contain the content path for the specified content. |
| List localContentList = new ArrayList(); |
| |
| // Find class path meta-data. |
| String classPath = (String) module.getHeaders().get(FelixConstants.BUNDLE_CLASSPATH); |
| // Parse the class path into strings. |
| List<String> classPathStrings = ManifestParser.parseDelimitedString( |
| classPath, FelixConstants.CLASS_PATH_SEPARATOR); |
| |
| if (classPathStrings == null) |
| { |
| classPathStrings = new ArrayList<String>(0); |
| } |
| |
| // Create the bundles class path. |
| for (int i = 0; i < classPathStrings.size(); i++) |
| { |
| // Remove any leading slash, since all bundle class path |
| // entries are relative to the root of the bundle. |
| classPathStrings.set(i, (classPathStrings.get(i).startsWith("/")) |
| ? classPathStrings.get(i).substring(1) |
| : classPathStrings.get(i)); |
| |
| // Check for the bundle itself on the class path. |
| if (classPathStrings.get(i).equals(FelixConstants.CLASS_PATH_DOT)) |
| { |
| localContentList.add(content); |
| } |
| else |
| { |
| // Try to find the embedded class path entry in the current |
| // content. |
| Content embeddedContent = content.getEntryAsContent(classPathStrings.get(i)); |
| // If the embedded class path entry was not found, it might be |
| // in one of the fragments if the current content is the bundle, |
| // so try to search the fragments if necessary. |
| for (int fragIdx = 0; |
| searchFragments && (embeddedContent == null) |
| && (m_fragmentContents != null) && (fragIdx < m_fragmentContents.length); |
| fragIdx++) |
| { |
| embeddedContent = |
| m_fragmentContents[fragIdx].getEntryAsContent(classPathStrings.get(i)); |
| } |
| // If we found the embedded content, then add it to the |
| // class path content list. |
| if (embeddedContent != null) |
| { |
| localContentList.add(embeddedContent); |
| } |
| else |
| { |
| // TODO: FRAMEWORK - Per the spec, this should fire a FrameworkEvent.INFO event; |
| // need to create an "Eventer" class like "Logger" perhaps. |
| m_logger.log(m_bundle, Logger.LOG_INFO, |
| "Class path entry not found: " |
| + classPathStrings.get(i)); |
| } |
| } |
| } |
| |
| // If there is nothing on the class path, then include |
| // "." by default, as per the spec. |
| if (localContentList.size() == 0) |
| { |
| localContentList.add(content); |
| } |
| |
| // Now add the local contents to the global content list and return it. |
| contentList.addAll(localContentList); |
| return contentList; |
| } |
| |
| public Class getClassByDelegation(String name) throws ClassNotFoundException |
| { |
| // We do not call getClassLoader().loadClass() for arrays because |
| // it does not correctly handle array types, which is necessary in |
| // cases like deserialization using a wrapper class loader. |
| if ((name != null) && (name.length() > 0) && (name.charAt(0) == '[')) |
| { |
| return Class.forName(name, false, getClassLoader()); |
| } |
| return getClassLoader().loadClass(name); |
| } |
| |
| public URL getResourceByDelegation(String name) |
| { |
| try |
| { |
| return (URL) findClassOrResourceByDelegation(name, false); |
| } |
| catch (ClassNotFoundException ex) |
| { |
| // This should never be thrown because we are loading resources. |
| } |
| catch (ResourceNotFoundException ex) |
| { |
| m_logger.log(m_bundle, |
| Logger.LOG_DEBUG, |
| ex.getMessage()); |
| } |
| return null; |
| } |
| |
| private Object findClassOrResourceByDelegation(String name, boolean isClass) |
| throws ClassNotFoundException, ResourceNotFoundException |
| { |
| Object result = null; |
| |
| Set requestSet = (Set) m_cycleCheck.get(); |
| if (requestSet == null) |
| { |
| requestSet = new HashSet(); |
| m_cycleCheck.set(requestSet); |
| } |
| if (requestSet.add(name)) |
| { |
| try |
| { |
| // First, try to resolve the originating module. |
| m_resolver.resolve(this); |
| |
| // Get the package of the target class/resource. |
| String pkgName = (isClass) |
| ? Util.getClassPackage(name) |
| : Util.getResourcePackage(name); |
| |
| // Delegate any packages listed in the boot delegation |
| // property to the parent class loader. |
| if (shouldBootDelegate(pkgName)) |
| { |
| try |
| { |
| // Get the appropriate class loader for delegation. |
| ClassLoader bdcl = getBootDelegationClassLoader(); |
| result = (isClass) |
| ? (Object) bdcl.loadClass(name) |
| : (Object) bdcl.getResource(name); |
| // If this is a java.* package, then always terminate the |
| // search; otherwise, continue to look locally if not found. |
| if (pkgName.startsWith("java.") || (result != null)) |
| { |
| return result; |
| } |
| } |
| catch (ClassNotFoundException ex) |
| { |
| // If this is a java.* package, then always terminate the |
| // search; otherwise, continue to look locally if not found. |
| if (pkgName.startsWith("java.")) |
| { |
| throw ex; |
| } |
| } |
| } |
| |
| // Look in the module's imports. Note that the search may |
| // be aborted if this method throws an exception, otherwise |
| // it continues if a null is returned. |
| result = searchImports(name, isClass); |
| |
| // If not found, try the module's own class path. |
| if (result == null) |
| { |
| result = (isClass) |
| ? (Object) getClassLoader().findClass(name) |
| : (Object) getResourceLocal(name); |
| |
| // If still not found, then try the module's dynamic imports. |
| if (result == null) |
| { |
| result = searchDynamicImports(name, pkgName, isClass); |
| } |
| } |
| } |
| catch (ResolveException ex) |
| { |
| if (isClass) |
| { |
| // We do not use the resolve exception as the |
| // cause of the exception, since this would |
| // potentially leak internal module information. |
| throw new ClassNotFoundException( |
| name + " not found because " |
| + getBundle() |
| + " cannot resolve: " |
| + ex.getRequirement()); |
| } |
| else |
| { |
| // The spec states that if the bundle cannot be resolved, then |
| // only the local bundle's resources should be searched. So we |
| // will ask the module's own class path. |
| URL url = getResourceLocal(name); |
| if (url != null) |
| { |
| return url; |
| } |
| |
| // We need to throw a resource not found exception. |
| throw new ResourceNotFoundException( |
| name + " not found because " |
| + getBundle() |
| + " cannot resolve: " |
| + ex.getRequirement()); |
| } |
| } |
| finally |
| { |
| requestSet.remove(name); |
| } |
| } |
| else |
| { |
| // If a cycle is detected, we should return null to break the |
| // cycle. This should only ever be return to internal class |
| // loading code and not to the actual instigator of the class load. |
| return null; |
| } |
| |
| if (result == null) |
| { |
| if (isClass) |
| { |
| throw new ClassNotFoundException( |
| name + " not found by " + this.getBundle()); |
| } |
| else |
| { |
| throw new ResourceNotFoundException( |
| name + " not found by " + this.getBundle()); |
| } |
| } |
| |
| return result; |
| } |
| |
| URL getResourceLocal(String name) |
| { |
| URL url = null; |
| |
| // Remove leading slash, if present, but special case |
| // "/" so that it returns a root URL...this isn't very |
| // clean or meaninful, but the Spring guys want it. |
| if (name.equals("/")) |
| { |
| // Just pick a class path index since it doesn't really matter. |
| url = createURL(1, name); |
| } |
| else if (name.startsWith("/")) |
| { |
| name = name.substring(1); |
| } |
| |
| // Check the module class path. |
| Content[] contentPath = getContentPath(); |
| for (int i = 0; |
| (url == null) && |
| (i < contentPath.length); i++) |
| { |
| if (contentPath[i].hasEntry(name)) |
| { |
| url = createURL(i + 1, name); |
| } |
| } |
| |
| return url; |
| } |
| |
| public Enumeration getResourcesByDelegation(String name) |
| { |
| Set requestSet = (Set) m_cycleCheck.get(); |
| if (requestSet == null) |
| { |
| requestSet = new HashSet(); |
| m_cycleCheck.set(requestSet); |
| } |
| if (!requestSet.contains(name)) |
| { |
| requestSet.add(name); |
| try |
| { |
| return findResourcesByDelegation(name); |
| } |
| finally |
| { |
| requestSet.remove(name); |
| } |
| } |
| |
| return null; |
| } |
| |
| private Enumeration findResourcesByDelegation(String name) |
| { |
| Enumeration urls = null; |
| List completeUrlList = new ArrayList(); |
| |
| // First, try to resolve the originating module. |
| try |
| { |
| m_resolver.resolve(this); |
| } |
| catch (ResolveException ex) |
| { |
| // The spec states that if the bundle cannot be resolved, then |
| // only the local bundle's resources should be searched. So we |
| // will ask the module's own class path. |
| return getResourcesLocal(name); |
| } |
| |
| // Get the package of the target class/resource. |
| String pkgName = Util.getResourcePackage(name); |
| |
| // Delegate any packages listed in the boot delegation |
| // property to the parent class loader. |
| if (shouldBootDelegate(pkgName)) |
| { |
| try |
| { |
| // Get the appropriate class loader for delegation. |
| ClassLoader bdcl = getBootDelegationClassLoader(); |
| urls = bdcl.getResources(name); |
| } |
| catch (IOException ex) |
| { |
| // This shouldn't happen and even if it does, there |
| // is nothing we can do, so just ignore it. |
| } |
| // If this is a java.* package, then always terminate the |
| // search; otherwise, continue to look locally. |
| if (pkgName.startsWith("java.")) |
| { |
| return urls; |
| } |
| |
| completeUrlList.add(urls); |
| } |
| |
| // Look in the module's imports. |
| // We delegate to the module's wires for the resources. |
| // If any resources are found, this means that the package of these |
| // resources is imported, we must not keep looking since we do not |
| // support split-packages. |
| |
| // Note that the search may be aborted if this method throws an |
| // exception, otherwise it continues if a null is returned. |
| List<Wire> wires = getWires(); |
| for (int i = 0; (wires != null) && (i < wires.size()); i++) |
| { |
| if (wires.get(i) instanceof WireImpl) |
| { |
| try |
| { |
| // If we find the class or resource, then return it. |
| urls = wires.get(i).getResources(name); |
| } |
| catch (ResourceNotFoundException ex) |
| { |
| urls = null; |
| } |
| if (urls != null) |
| { |
| completeUrlList.add(urls); |
| return new CompoundEnumeration((Enumeration[]) |
| completeUrlList.toArray(new Enumeration[completeUrlList.size()])); |
| } |
| } |
| } |
| |
| // See whether we can get the resource from the required bundles and |
| // regardless of whether or not this is the case continue to the next |
| // step potentially passing on the result of this search (if any). |
| for (int i = 0; (wires != null) && (i < wires.size()); i++) |
| { |
| if (wires.get(i) instanceof WireModuleImpl) |
| { |
| try |
| { |
| // If we find the class or resource, then add it. |
| urls = wires.get(i).getResources(name); |
| } |
| catch (ResourceNotFoundException ex) |
| { |
| urls = null; |
| } |
| if (urls != null) |
| { |
| completeUrlList.add(urls); |
| } |
| } |
| } |
| |
| // Try the module's own class path. If we can find the resource then |
| // return it together with the results from the other searches else |
| // try to look into the dynamic imports. |
| urls = getResourcesLocal(name); |
| if ((urls != null) && (urls.hasMoreElements())) |
| { |
| completeUrlList.add(urls); |
| } |
| else |
| { |
| // If not found, then try the module's dynamic imports. |
| // At this point, the module's imports were searched and so was the |
| // the module's content. Now we make an attempt to load the |
| // class/resource via a dynamic import, if possible. |
| Wire wire = null; |
| try |
| { |
| wire = m_resolver.resolve(this, pkgName); |
| } |
| catch (ResolveException ex) |
| { |
| // Ignore this since it is likely normal. |
| } |
| if (wire != null) |
| { |
| try |
| { |
| urls = wire.getResources(name); |
| } |
| catch (ResourceNotFoundException ex) |
| { |
| urls = null; |
| } |
| if (urls != null) |
| { |
| completeUrlList.add(urls); |
| } |
| } |
| } |
| |
| return new CompoundEnumeration((Enumeration[]) |
| completeUrlList.toArray(new Enumeration[completeUrlList.size()])); |
| } |
| |
| private Enumeration getResourcesLocal(String name) |
| { |
| List l = new ArrayList(); |
| |
| // Special case "/" so that it returns a root URLs for |
| // each bundle class path entry...this isn't very |
| // clean or meaningful, but the Spring guys want it. |
| final Content[] contentPath = getContentPath(); |
| if (name.equals("/")) |
| { |
| for (int i = 0; i < contentPath.length; i++) |
| { |
| l.add(createURL(i + 1, name)); |
| } |
| } |
| else |
| { |
| // Remove leading slash, if present. |
| if (name.startsWith("/")) |
| { |
| name = name.substring(1); |
| } |
| |
| // Check the module class path. |
| for (int i = 0; i < contentPath.length; i++) |
| { |
| if (contentPath[i].hasEntry(name)) |
| { |
| // Use the class path index + 1 for creating the path so |
| // that we can differentiate between module content URLs |
| // (where the path will start with 0) and module class |
| // path URLs. |
| l.add(createURL(i + 1, name)); |
| } |
| } |
| } |
| |
| return new IteratorToEnumeration(l.iterator()); |
| } |
| |
| // TODO: API: Investigate how to handle this better, perhaps we need |
| // multiple URL policies, one for content -- one for class path. |
| public URL getEntry(String name) |
| { |
| URL url = null; |
| |
| // Check for the special case of "/", which represents |
| // the root of the bundle according to the spec. |
| if (name.equals("/")) |
| { |
| url = createURL(0, "/"); |
| } |
| |
| if (url == null) |
| { |
| // Remove leading slash, if present. |
| if (name.startsWith("/")) |
| { |
| name = name.substring(1); |
| } |
| |
| // Check the module content. |
| if (getContent().hasEntry(name)) |
| { |
| // Module content URLs start with 0, whereas module |
| // class path URLs start with the index into the class |
| // path + 1. |
| url = createURL(0, name); |
| } |
| } |
| |
| return url; |
| } |
| |
| public boolean hasInputStream(int index, String urlPath) |
| { |
| if (urlPath.startsWith("/")) |
| { |
| urlPath = urlPath.substring(1); |
| } |
| if (index == 0) |
| { |
| return m_content.hasEntry(urlPath); |
| } |
| return getContentPath()[index - 1].hasEntry(urlPath); |
| } |
| |
| public InputStream getInputStream(int index, String urlPath) |
| throws IOException |
| { |
| if (urlPath.startsWith("/")) |
| { |
| urlPath = urlPath.substring(1); |
| } |
| if (index == 0) |
| { |
| return m_content.getEntryAsStream(urlPath); |
| } |
| return getContentPath()[index - 1].getEntryAsStream(urlPath); |
| } |
| |
| public URL getLocalURL(int index, String urlPath) |
| { |
| if (urlPath.startsWith("/")) |
| { |
| urlPath = urlPath.substring(1); |
| } |
| if (index == 0) |
| { |
| return m_content.getEntryAsURL(urlPath); |
| } |
| return getContentPath()[index - 1].getEntryAsURL(urlPath); |
| } |
| |
| private URL createURL(int port, String path) |
| { |
| if (useJarUrl()) |
| { |
| return getLocalURL(port, path); |
| } |
| // Add a slash if there is one already, otherwise |
| // the is no slash separating the host from the file |
| // in the resulting URL. |
| if (!path.startsWith("/")) |
| { |
| path = "/" + path; |
| } |
| |
| try |
| { |
| return m_secureAction.createURL(null, |
| FelixConstants.BUNDLE_URL_PROTOCOL + "://" + |
| m_id + ":" + port + path, m_streamHandler); |
| } |
| catch (MalformedURLException ex) |
| { |
| m_logger.log(m_bundle, |
| Logger.LOG_ERROR, |
| "Unable to create resource URL.", |
| ex); |
| } |
| return null; |
| } |
| |
| private boolean useJarUrl() |
| { |
| String val = (String) ((BundleImpl) getBundle()).getFramework() |
| .getConfig().get("org.apache.felix.jarurls"); |
| return m_jarUrl.get() && Boolean.parseBoolean(val); |
| } |
| |
| // |
| // Fragment and dependency management methods. |
| // |
| |
| public synchronized List<Module> getFragments() |
| { |
| return m_fragments; |
| } |
| |
| public synchronized void attachFragments(List<Module> fragments) throws Exception |
| { |
| // Remove module from old fragment dependencies. |
| // We will generally only remove module fragment |
| // dependencies when we are uninstalling the module. |
| for (int i = 0; (m_fragments != null) && (i < m_fragments.size()); i++) |
| { |
| ((ModuleImpl) m_fragments.get(i)).removeDependentHost(this); |
| } |
| |
| // Remove cached capabilities and requirements. |
| m_cachedCapabilities = null; |
| m_cachedRequirements = null; |
| m_cachedDynamicRequirements = null; |
| |
| // Update the dependencies on the new fragments. |
| m_fragments = fragments; |
| |
| // We need to add ourself as a dependent of each fragment |
| // module. We also need to create an array of fragment contents |
| // to attach to our content loader. |
| if (m_fragments != null) |
| { |
| Content[] fragmentContents = new Content[m_fragments.size()]; |
| for (int i = 0; (m_fragments != null) && (i < m_fragments.size()); i++) |
| { |
| ((ModuleImpl) m_fragments.get(i)).addDependentHost(this); |
| fragmentContents[i] = |
| m_fragments.get(i).getContent() |
| .getEntryAsContent(FelixConstants.CLASS_PATH_DOT); |
| } |
| // Now attach the fragment contents to our content loader. |
| attachFragmentContents(fragmentContents); |
| } |
| } |
| |
| // This must be called holding the object lock. |
| private void attachFragmentContents(Content[] fragmentContents) |
| throws Exception |
| { |
| // Close existing fragment contents. |
| if (m_fragmentContents != null) |
| { |
| for (int i = 0; i < m_fragmentContents.length; i++) |
| { |
| m_fragmentContents[i].close(); |
| } |
| } |
| m_fragmentContents = fragmentContents; |
| |
| if (m_contentPath != null) |
| { |
| for (int i = 0; i < m_contentPath.length; i++) |
| { |
| m_contentPath[i].close(); |
| } |
| } |
| m_contentPath = initializeContentPath(); |
| } |
| |
| public synchronized List<Module> getDependentHosts() |
| { |
| return m_dependentHosts; |
| } |
| |
| public synchronized void addDependentHost(Module module) |
| { |
| if (!m_dependentHosts.contains(module)) |
| { |
| m_dependentHosts.add(module); |
| } |
| } |
| |
| public synchronized void removeDependentHost(Module module) |
| { |
| m_dependentHosts.remove(module); |
| } |
| |
| public synchronized List<Module> getDependentImporters() |
| { |
| return m_dependentImporters; |
| } |
| |
| public synchronized void addDependentImporter(Module module) |
| { |
| if (!m_dependentImporters.contains(module)) |
| { |
| m_dependentImporters.add(module); |
| } |
| } |
| |
| public synchronized void removeDependentImporter(Module module) |
| { |
| m_dependentImporters.remove(module); |
| } |
| |
| public synchronized List<Module> getDependentRequirers() |
| { |
| return m_dependentRequirers; |
| } |
| |
| public synchronized void addDependentRequirer(Module module) |
| { |
| if (!m_dependentRequirers.contains(module)) |
| { |
| m_dependentRequirers.add(module); |
| } |
| } |
| |
| public synchronized void removeDependentRequirer(Module module) |
| { |
| m_dependentRequirers.remove(module); |
| } |
| |
| public synchronized List<Module> getDependents() |
| { |
| List<Module> dependents = new ArrayList<Module> |
| (m_dependentHosts.size() + m_dependentImporters.size() + m_dependentRequirers.size()); |
| dependents.addAll(m_dependentHosts); |
| dependents.addAll(m_dependentImporters); |
| dependents.addAll(m_dependentRequirers); |
| return dependents; |
| } |
| |
| public synchronized void close() |
| { |
| m_content.close(); |
| for (int i = 0; (m_contentPath != null) && (i < m_contentPath.length); i++) |
| { |
| m_contentPath[i].close(); |
| } |
| for (int i = 0; (m_fragmentContents != null) && (i < m_fragmentContents.length); i++) |
| { |
| m_fragmentContents[i].close(); |
| } |
| m_classLoader = null; |
| } |
| |
| public synchronized void setSecurityContext(Object securityContext) |
| { |
| m_protectionDomain = (ProtectionDomain) securityContext; |
| } |
| |
| public synchronized Object getSecurityContext() |
| { |
| return m_protectionDomain; |
| } |
| |
| public String toString() |
| { |
| return m_id; |
| } |
| |
| private synchronized ModuleClassLoader getClassLoader() |
| { |
| if (m_classLoader == null) |
| { |
| // Determine which class loader to use based on which |
| // Java platform we are running on. |
| Class clazz; |
| if (m_isPreJava5) |
| { |
| clazz = ModuleClassLoader.class; |
| } |
| else |
| { |
| try |
| { |
| clazz = ModuleClassLoaderJava5.class; |
| } |
| catch (Throwable th) |
| { |
| // If we are on pre-Java5 then we will get a verify error |
| // here since we try to override a getResources() which is |
| // a final method in pre-Java5. |
| m_isPreJava5 = true; |
| clazz = ModuleClassLoader.class; |
| } |
| } |
| |
| // Use SecureAction to create the class loader if security is |
| // enabled; otherwise, create it directly. |
| try |
| { |
| Constructor ctor = (Constructor) m_secureAction.getConstructor( |
| clazz, new Class[] { ModuleImpl.class, ClassLoader.class }); |
| m_classLoader = (ModuleClassLoader) |
| m_secureAction.invoke(ctor, |
| new Object[] { this, determineParentClassLoader() }); |
| } |
| catch (Exception ex) |
| { |
| throw new RuntimeException("Unable to create module class loader: " |
| + ex.getMessage() + " [" + ex.getClass().getName() + "]"); |
| } |
| } |
| return m_classLoader; |
| } |
| |
| private ClassLoader determineParentClassLoader() |
| { |
| // Determine the class loader's parent based on the |
| // configuration property; use boot class loader by |
| // default. |
| String cfg = (String) m_configMap.get(Constants.FRAMEWORK_BUNDLE_PARENT); |
| cfg = (cfg == null) ? Constants.FRAMEWORK_BUNDLE_PARENT_BOOT : cfg; |
| final ClassLoader parent; |
| if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_APP)) |
| { |
| parent = m_secureAction.getSystemClassLoader(); |
| } |
| else if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_EXT)) |
| { |
| parent = m_secureAction.getSystemClassLoader().getParent(); |
| } |
| else if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK)) |
| { |
| parent = ModuleImpl.class.getClassLoader(); |
| } |
| // On Android we cannot set the parent class loader to be null, so |
| // we special case that situation here and set it to the system |
| // class loader by default instead, which is not really spec. |
| else if (m_bootClassLoader == null) |
| { |
| parent = m_secureAction.getSystemClassLoader(); |
| } |
| else |
| { |
| parent = null; |
| } |
| return parent; |
| } |
| |
| private Object searchImports(String name, boolean isClass) |
| throws ClassNotFoundException, ResourceNotFoundException |
| { |
| // We delegate to the module's wires to find the class or resource. |
| List<Wire> wires = getWires(); |
| for (int i = 0; (wires != null) && (i < wires.size()); i++) |
| { |
| // If we find the class or resource, then return it. |
| Object result = (isClass) |
| ? (Object) wires.get(i).getClass(name) |
| : (Object) wires.get(i).getResource(name); |
| if (result != null) |
| { |
| return result; |
| } |
| } |
| |
| return null; |
| } |
| |
| private Object searchDynamicImports( |
| final String name, String pkgName, final boolean isClass) |
| throws ClassNotFoundException, ResourceNotFoundException |
| { |
| // At this point, the module's imports were searched and so was the |
| // the module's content. Now we make an attempt to load the |
| // class/resource via a dynamic import, if possible. |
| Wire wire = null; |
| try |
| { |
| wire = m_resolver.resolve(this, pkgName); |
| } |
| catch (ResolveException ex) |
| { |
| // Ignore this since it is likely normal. |
| } |
| |
| // If the dynamic import was successful, then this initial |
| // time we must directly return the result from dynamically |
| // created wire, but subsequent requests for classes/resources |
| // in the associated package will be processed as part of |
| // normal static imports. |
| if (wire != null) |
| { |
| // Return the class or resource. |
| return (isClass) |
| ? (Object) wire.getClass(name) |
| : (Object) wire.getResource(name); |
| } |
| |
| // If implicit boot delegation is enabled, then try to guess whether |
| // we should boot delegate. |
| if (m_implicitBootDelegation) |
| { |
| // At this point, the class/resource could not be found by the bundle's |
| // static or dynamic imports, nor its own content. Before we throw |
| // an exception, we will try to determine if the instigator of the |
| // class/resource load was a class from a bundle or not. This is necessary |
| // because the specification mandates that classes on the class path |
| // should be hidden (except for java.*), but it does allow for these |
| // classes/resources to be exposed by the system bundle as an export. |
| // However, in some situations classes on the class path make the faulty |
| // assumption that they can access everything on the class path from |
| // every other class loader that they come in contact with. This is |
| // not true if the class loader in question is from a bundle. Thus, |
| // this code tries to detect that situation. If the class instigating |
| // the load request was NOT from a bundle, then we will make the |
| // assumption that the caller actually wanted to use the parent class |
| // loader and we will delegate to it. If the class was |
| // from a bundle, then we will enforce strict class loading rules |
| // for the bundle and throw an exception. |
| |
| // Get the class context to see the classes on the stack. |
| final Class[] classes = m_sm.getClassContext(); |
| try |
| { |
| if (System.getSecurityManager() != null) |
| { |
| return AccessController |
| .doPrivileged(new PrivilegedExceptionAction() |
| { |
| public Object run() throws Exception |
| { |
| return doImplicitBootDelegation(classes, name, |
| isClass); |
| } |
| }); |
| } |
| else |
| { |
| return doImplicitBootDelegation(classes, name, isClass); |
| } |
| } |
| catch (PrivilegedActionException ex) |
| { |
| Exception cause = ex.getException(); |
| if (cause instanceof ClassNotFoundException) |
| { |
| throw (ClassNotFoundException) cause; |
| } |
| else |
| { |
| throw (ResourceNotFoundException) cause; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private Object doImplicitBootDelegation(Class[] classes, String name, boolean isClass) |
| throws ClassNotFoundException, ResourceNotFoundException |
| { |
| // Start from 1 to skip security manager class. |
| for (int i = 1; i < classes.length; i++) |
| { |
| // Find the first class on the call stack that is not from |
| // the class loader that loaded the Felix classes or is not |
| // a class loader or class itself, because we want to ignore |
| // calls to ClassLoader.loadClass() and Class.forName() since |
| // we are trying to find out who instigated the class load. |
| // Also ignore inner classes of class loaders, since we can |
| // assume they are a class loader too. |
| |
| // TODO: FRAMEWORK - This check is a hack and we should see if we can think |
| // of another way to do it, since it won't necessarily work in all situations. |
| // Since Felix uses threads for changing the start level |
| // and refreshing packages, it is possible that there is no |
| // module classes on the call stack; therefore, as soon as we |
| // see Thread on the call stack we exit this loop. Other cases |
| // where modules actually use threads are not an issue because |
| // the module classes will be on the call stack before the |
| // Thread class. |
| if (Thread.class.equals(classes[i])) |
| { |
| break; |
| } |
| else if (isClassNotLoadedFromBundle(classes[i])) |
| { |
| // If the instigating class was not from a bundle, |
| // then delegate to the parent class loader; otherwise, |
| // break out of loop and return null. |
| boolean delegate = true; |
| ClassLoader last = null; |
| for (ClassLoader cl = classes[i].getClassLoader(); |
| (cl != null) && (last != cl); |
| cl = cl.getClass().getClassLoader()) |
| { |
| last = cl; |
| if (ModuleClassLoader.class.isInstance(cl)) |
| { |
| delegate = false; |
| break; |
| } |
| } |
| // Delegate to the parent class loader unless this call |
| // is due to outside code calling a method on the bundle |
| // interface (e.g., Bundle.loadClass()). |
| if (delegate && !Bundle.class.isAssignableFrom(classes[i - 1])) |
| { |
| try |
| { |
| // Return the class or resource from the parent class loader. |
| return (isClass) |
| ? (Object) this.getClass().getClassLoader().loadClass(name) |
| : (Object) this.getClass().getClassLoader().getResource(name); |
| } |
| catch (NoClassDefFoundError ex) |
| { |
| // Ignore, will return null |
| } |
| } |
| break; |
| } |
| } |
| |
| return null; |
| } |
| |
| private boolean isClassNotLoadedFromBundle(Class clazz) |
| { |
| // If this is an inner class, try to get the enclosing class |
| // because we can assume that inner classes of class loaders |
| // are really just the class loader and we should ignore them. |
| Class enclosing = getEnclosingClass(clazz); |
| return (this.getClass().getClassLoader() != enclosing.getClassLoader()) |
| // Do the test on the enclosing class |
| && !ClassLoader.class.isAssignableFrom(enclosing) |
| && !Class.class.equals(enclosing) |
| && !Proxy.class.equals(enclosing) |
| // Do the test on the class itself |
| && !ClassLoader.class.isAssignableFrom(clazz) |
| && !Class.class.equals(clazz) |
| && !Proxy.class.equals(clazz); |
| } |
| |
| private static Class getEnclosingClass(Class clazz) |
| { |
| // This code determines if the class is an inner class and if so |
| // returns the enclosing class. At one point in time this code used |
| // Class.getEnclosingClass() for JDKs > 1.5, but due to a bug in the |
| // JDK which caused invalid ClassCircularityErrors we had to remove it. |
| int idx = clazz.getName().lastIndexOf('$'); |
| if (idx > 0) |
| { |
| ClassLoader cl = (clazz.getClassLoader() != null) |
| ? clazz.getClassLoader() : ClassLoader.getSystemClassLoader(); |
| try |
| { |
| Class enclosing = cl.loadClass(clazz.getName().substring(0, idx)); |
| clazz = (enclosing != null) ? enclosing : clazz; |
| } |
| catch (Throwable t) |
| { |
| // Ignore all problems since we are trying to load a class |
| // inside the class loader and this can lead to |
| // ClassCircularityError, for example. |
| } |
| } |
| |
| return clazz; |
| } |
| |
| boolean shouldBootDelegate(String pkgName) |
| { |
| // Always boot delegate if the bundle has a configured |
| // boot class loader. |
| if (m_bootClassLoader != m_defBootClassLoader) |
| { |
| return true; |
| } |
| |
| boolean result = false; |
| |
| // Only consider delegation if we have a package name, since |
| // we don't want to promote the default package. The spec does |
| // not take a stand on this issue. |
| if (pkgName.length() > 0) |
| { |
| for (int i = 0; !result && (i < m_bootPkgs.length); i++) |
| { |
| // Check if the boot package is wildcarded. |
| // A wildcarded boot package will be in the form "foo.", |
| // so a matching subpackage will start with "foo.", e.g., |
| // "foo.bar". |
| if (m_bootPkgWildcards[i] && pkgName.startsWith(m_bootPkgs[i])) |
| { |
| return true; |
| } |
| // If not wildcarded, then check for an exact match. |
| else if (m_bootPkgs[i].equals(pkgName)) |
| { |
| return true; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| ClassLoader getBootDelegationClassLoader() |
| { |
| // Get the appropriate class loader for delegation. |
| ClassLoader parent = (m_classLoader == null) |
| ? determineParentClassLoader() : m_classLoader.getParent(); |
| return (parent == null) ? m_bootClassLoader : parent; |
| } |
| |
| private static final Constructor m_dexFileClassConstructor; |
| private static final Method m_dexFileClassLoadDex; |
| private static final Method m_dexFileClassLoadClass; |
| |
| static |
| { |
| Constructor dexFileClassConstructor = null; |
| Method dexFileClassLoadDex = null; |
| Method dexFileClassLoadClass = null; |
| try |
| { |
| Class dexFileClass; |
| try |
| { |
| dexFileClass = Class.forName("dalvik.system.DexFile"); |
| } |
| catch (Exception ex) |
| { |
| dexFileClass = Class.forName("android.dalvik.DexFile"); |
| } |
| |
| try |
| { |
| dexFileClassLoadDex = dexFileClass.getMethod("loadDex", |
| new Class[]{String.class, String.class, Integer.TYPE}); |
| } |
| catch (Exception ex) |
| { |
| // Nothing we need to do |
| } |
| dexFileClassConstructor = dexFileClass.getConstructor( |
| new Class[] { java.io.File.class }); |
| dexFileClassLoadClass = dexFileClass.getMethod("loadClass", |
| new Class[] { String.class, ClassLoader.class }); |
| } |
| catch (Throwable ex) |
| { |
| dexFileClassConstructor = null; |
| dexFileClassLoadDex = null; |
| dexFileClassLoadClass = null; |
| } |
| m_dexFileClassConstructor = dexFileClassConstructor; |
| m_dexFileClassLoadDex= dexFileClassLoadDex; |
| m_dexFileClassLoadClass = dexFileClassLoadClass; |
| } |
| |
| public class ModuleClassLoaderJava5 extends ModuleClassLoader |
| { |
| public ModuleClassLoaderJava5(ClassLoader parent) |
| { |
| super(parent); |
| } |
| |
| public Enumeration getResources(String name) |
| { |
| return ModuleImpl.this.getResourcesByDelegation(name); |
| } |
| |
| protected Enumeration findResources(String name) |
| { |
| return getResourcesLocal(name); |
| } |
| } |
| |
| public class ModuleClassLoader extends SecureClassLoader implements BundleReference |
| { |
| private final Map m_jarContentToDexFile; |
| private Object[][] m_cachedLibs = new Object[0][]; |
| private static final int LIBNAME_IDX = 0; |
| private static final int LIBPATH_IDX = 1; |
| |
| public ModuleClassLoader(ClassLoader parent) |
| { |
| super(parent); |
| if (m_dexFileClassLoadClass != null) |
| { |
| m_jarContentToDexFile = new HashMap(); |
| } |
| else |
| { |
| m_jarContentToDexFile = null; |
| } |
| } |
| |
| public Bundle getBundle() |
| { |
| return ModuleImpl.this.getBundle(); |
| } |
| |
| protected Class loadClass(String name, boolean resolve) |
| throws ClassNotFoundException |
| { |
| Class clazz = null; |
| |
| // Make sure the class was not already loaded. |
| synchronized (this) |
| { |
| clazz = findLoadedClass(name); |
| } |
| |
| if (clazz == null) |
| { |
| try |
| { |
| clazz = (Class) findClassOrResourceByDelegation(name, true); |
| } |
| catch (ResourceNotFoundException ex) |
| { |
| // This should never happen since we are asking for a class, |
| // so just ignore it. |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| ClassNotFoundException ex = cnfe; |
| String msg = name; |
| if (m_logger.getLogLevel() >= Logger.LOG_DEBUG) |
| { |
| msg = diagnoseClassLoadError(m_resolver, ModuleImpl.this, name); |
| ex = (msg != null) |
| ? new ClassNotFoundException(msg, cnfe) |
| : ex; |
| } |
| throw ex; |
| } |
| } |
| |
| // Resolve the class and return it. |
| if (resolve) |
| { |
| resolveClass(clazz); |
| } |
| return clazz; |
| } |
| |
| protected Class findClass(String name) throws ClassNotFoundException |
| { |
| Class clazz = null; |
| |
| // Search for class in module. |
| if (clazz == null) |
| { |
| String actual = name.replace('.', '/') + ".class"; |
| |
| byte[] bytes = null; |
| |
| // Check the module class path. |
| Content[] contentPath = getContentPath(); |
| Content content = null; |
| for (int i = 0; |
| (bytes == null) && |
| (i < contentPath.length); i++) |
| { |
| bytes = contentPath[i].getEntryAsBytes(actual); |
| content = contentPath[i]; |
| } |
| |
| if (bytes != null) |
| { |
| // Get package name. |
| String pkgName = Util.getClassPackage(name); |
| |
| // Before we actually attempt to define the class, grab |
| // the lock for this class loader and make sure than no |
| // other thread has defined this class in the meantime. |
| synchronized (this) |
| { |
| clazz = findLoadedClass(name); |
| |
| if (clazz == null) |
| { |
| int activationPolicy = |
| ((BundleImpl) getBundle()).isDeclaredActivationPolicyUsed() |
| ? ((BundleImpl) getBundle()).getCurrentModule().getDeclaredActivationPolicy() |
| : Module.EAGER_ACTIVATION; |
| |
| // If the module is using deferred activation, then if |
| // we load this class from this module we need to activate |
| // the module before returning the class. We will short |
| // circuit the trigger matching if the trigger is already |
| // tripped. |
| boolean isTriggerClass = m_isActivationTriggered |
| ? false : isActivationTrigger(pkgName); |
| if (!m_isActivationTriggered |
| && isTriggerClass |
| && (activationPolicy == Module.LAZY_ACTIVATION) |
| && (getBundle().getState() == Bundle.STARTING)) |
| { |
| List deferredList = (List) m_deferredActivation.get(); |
| if (deferredList == null) |
| { |
| deferredList = new ArrayList(); |
| m_deferredActivation.set(deferredList); |
| } |
| deferredList.add(new Object[] { name, getBundle() }); |
| } |
| // We need to try to define a Package object for the class |
| // before we call defineClass() if we haven't already |
| // created it. |
| if (pkgName.length() > 0) |
| { |
| if (getPackage(pkgName) == null) |
| { |
| Object[] params = definePackage(pkgName); |
| if (params != null) |
| { |
| definePackage( |
| pkgName, |
| (String) params[0], |
| (String) params[1], |
| (String) params[2], |
| (String) params[3], |
| (String) params[4], |
| (String) params[5], |
| null); |
| } |
| else |
| { |
| definePackage(pkgName, null, null, |
| null, null, null, null, null); |
| } |
| } |
| } |
| |
| // If we can load the class from a dex file do so |
| if (content instanceof JarContent) |
| { |
| try |
| { |
| clazz = getDexFileClass((JarContent) content, name, this); |
| } |
| catch (Exception ex) |
| { |
| // Looks like we can't |
| } |
| } |
| |
| if (clazz == null) |
| { |
| // If we have a security context, then use it to |
| // define the class with it for security purposes, |
| // otherwise define the class without a protection domain. |
| if (m_protectionDomain != null) |
| { |
| clazz = defineClass(name, bytes, 0, bytes.length, |
| m_protectionDomain); |
| } |
| else |
| { |
| clazz = defineClass(name, bytes, 0, bytes.length); |
| } |
| } |
| |
| // At this point if we have a trigger class, then the deferred |
| // activation trigger has tripped. |
| if (!m_isActivationTriggered && isTriggerClass && (clazz != null)) |
| { |
| m_isActivationTriggered = true; |
| } |
| } |
| } |
| |
| // Perform deferred activation without holding the class loader lock, |
| // if the class we are returning is the instigating class. |
| List deferredList = (List) m_deferredActivation.get(); |
| if ((deferredList != null) |
| && (deferredList.size() > 0) |
| && ((Object[]) deferredList.get(0))[0].equals(name)) |
| { |
| for (int i = deferredList.size() - 1; i >= 0; i--) |
| { |
| try |
| { |
| ((BundleImpl) ((Object[]) deferredList.get(i))[1]).getFramework().activateBundle( |
| (BundleImpl) ((Object[]) deferredList.get(i))[1], true); |
| } |
| catch (BundleException ex) |
| { |
| ex.printStackTrace(); |
| } |
| } |
| deferredList.clear(); |
| } |
| } |
| } |
| |
| return clazz; |
| } |
| |
| private Object[] definePackage(String pkgName) |
| { |
| String spectitle = (String) m_headerMap.get("Specification-Title"); |
| String specversion = (String) m_headerMap.get("Specification-Version"); |
| String specvendor = (String) m_headerMap.get("Specification-Vendor"); |
| String impltitle = (String) m_headerMap.get("Implementation-Title"); |
| String implversion = (String) m_headerMap.get("Implementation-Version"); |
| String implvendor = (String) m_headerMap.get("Implementation-Vendor"); |
| if ((spectitle != null) |
| || (specversion != null) |
| || (specvendor != null) |
| || (impltitle != null) |
| || (implversion != null) |
| || (implvendor != null)) |
| { |
| return new Object[] { |
| spectitle, specversion, specvendor, impltitle, implversion, implvendor |
| }; |
| } |
| return null; |
| } |
| |
| private Class getDexFileClass(JarContent content, String name, ClassLoader loader) |
| throws Exception |
| { |
| if (m_jarContentToDexFile == null) |
| { |
| return null; |
| } |
| |
| Object dexFile = null; |
| |
| if (!m_jarContentToDexFile.containsKey(content)) |
| { |
| try |
| { |
| if (m_dexFileClassLoadDex != null) |
| { |
| dexFile = m_dexFileClassLoadDex.invoke(null, |
| new Object[]{content.getFile().getAbsolutePath(), |
| content.getFile().getAbsolutePath() + ".dex", new Integer(0)}); |
| } |
| else |
| { |
| dexFile = m_dexFileClassConstructor.newInstance( |
| new Object[] { content.getFile() }); |
| } |
| } |
| finally |
| { |
| m_jarContentToDexFile.put(content, dexFile); |
| } |
| } |
| else |
| { |
| dexFile = m_jarContentToDexFile.get(content); |
| } |
| |
| if (dexFile != null) |
| { |
| return (Class) m_dexFileClassLoadClass.invoke(dexFile, |
| new Object[] { name.replace('.','/'), loader }); |
| } |
| return null; |
| } |
| |
| public URL getResource(String name) |
| { |
| return ModuleImpl.this.getResourceByDelegation(name); |
| } |
| |
| protected URL findResource(String name) |
| { |
| return getResourceLocal(name); |
| } |
| |
| // The findResources() method should only look at the module itself, but |
| // instead it tries to delegate because in Java version prior to 1.5 the |
| // getResources() method was final and could not be overridden. We should |
| // override getResources() like getResource() to make it delegate, but we |
| // can't. As a workaround, we make findResources() delegate instead. |
| protected Enumeration findResources(String name) |
| { |
| return getResourcesByDelegation(name); |
| } |
| |
| protected String findLibrary(String name) |
| { |
| // Remove leading slash, if present. |
| if (name.startsWith("/")) |
| { |
| name = name.substring(1); |
| } |
| |
| String result = null; |
| // CONCURRENCY: In the long run, we might want to break this |
| // sync block in two to avoid manipulating the cache while |
| // holding the lock, but for now we will do it the simple way. |
| synchronized (this) |
| { |
| // Check to make sure we haven't already found this library. |
| for (int i = 0; (result == null) && (i < m_cachedLibs.length); i++) |
| { |
| if (m_cachedLibs[i][LIBNAME_IDX].equals(name)) |
| { |
| result = (String) m_cachedLibs[i][LIBPATH_IDX]; |
| } |
| } |
| |
| // If we don't have a cached result, see if we have a matching |
| // native library. |
| if (result == null) |
| { |
| List<R4Library> libs = getNativeLibraries(); |
| for (int libIdx = 0; (libs != null) && (libIdx < libs.size()); libIdx++) |
| { |
| if (libs.get(libIdx).match(m_configMap, name)) |
| { |
| // Search bundle content first for native library. |
| result = getContent().getEntryAsNativeLibrary( |
| libs.get(libIdx).getEntryName()); |
| // If not found, then search fragments in order. |
| for (int i = 0; |
| (result == null) && (m_fragmentContents != null) |
| && (i < m_fragmentContents.length); |
| i++) |
| { |
| result = m_fragmentContents[i].getEntryAsNativeLibrary( |
| libs.get(libIdx).getEntryName()); |
| } |
| } |
| } |
| |
| // Remember the result for future requests. |
| if (result != null) |
| { |
| Object[][] tmp = new Object[m_cachedLibs.length + 1][]; |
| System.arraycopy(m_cachedLibs, 0, tmp, 0, m_cachedLibs.length); |
| tmp[m_cachedLibs.length] = new Object[] { name, result }; |
| m_cachedLibs = tmp; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| public String toString() |
| { |
| return ModuleImpl.this.toString(); |
| } |
| } |
| |
| private static String diagnoseClassLoadError( |
| FelixResolver resolver, ModuleImpl module, String name) |
| { |
| // We will try to do some diagnostics here to help the developer |
| // deal with this exception. |
| |
| // Get package name. |
| String pkgName = Util.getClassPackage(name); |
| if (pkgName.length() == 0) |
| { |
| return null; |
| } |
| |
| // First, get the bundle string of the module doing the class loader. |
| String importer = module.getBundle().toString(); |
| |
| // Next, check to see if the module imports the package. |
| List<Wire> wires = module.getWires(); |
| for (int i = 0; (wires != null) && (i < wires.size()); i++) |
| { |
| if (wires.get(i).getCapability().getNamespace().equals(Capability.PACKAGE_NAMESPACE) && |
| wires.get(i).getCapability().getAttribute(Capability.PACKAGE_ATTR).getValue().equals(pkgName)) |
| { |
| String exporter = wires.get(i).getExporter().getBundle().toString(); |
| |
| StringBuffer sb = new StringBuffer("*** Package '"); |
| sb.append(pkgName); |
| sb.append("' is imported by bundle "); |
| sb.append(importer); |
| sb.append(" from bundle "); |
| sb.append(exporter); |
| sb.append(", but the exported package from bundle "); |
| sb.append(exporter); |
| sb.append(" does not contain the requested class '"); |
| sb.append(name); |
| sb.append("'. Please verify that the class name is correct in the importing bundle "); |
| sb.append(importer); |
| sb.append(" and/or that the exported package is correctly bundled in "); |
| sb.append(exporter); |
| sb.append(". ***"); |
| |
| return sb.toString(); |
| } |
| } |
| |
| // Next, check to see if the package was optionally imported and |
| // whether or not there is an exporter available. |
| List<Requirement> reqs = module.getRequirements(); |
| /* |
| * TODO: RB - Fix diagnostic message for optional imports. |
| for (int i = 0; (reqs != null) && (i < reqs.length); i++) |
| { |
| if (reqs[i].getName().equals(pkgName) && reqs[i].isOptional()) |
| { |
| // Try to see if there is an exporter available. |
| IModule[] exporters = getResolvedExporters(reqs[i], true); |
| exporters = (exporters.length == 0) |
| ? getUnresolvedExporters(reqs[i], true) : exporters; |
| |
| // An exporter might be available, but it may have attributes |
| // that do not match the importer's required attributes, so |
| // check that case by simply looking for an exporter of the |
| // desired package without any attributes. |
| if (exporters.length == 0) |
| { |
| IRequirement pkgReq = new Requirement( |
| ICapability.PACKAGE_NAMESPACE, "(package=" + pkgName + ")"); |
| exporters = getResolvedExporters(pkgReq, true); |
| exporters = (exporters.length == 0) |
| ? getUnresolvedExporters(pkgReq, true) : exporters; |
| } |
| |
| long expId = (exporters.length == 0) |
| ? -1 : Util.getBundleIdFromModuleId(exporters[0].getId()); |
| |
| StringBuffer sb = new StringBuffer("*** Class '"); |
| sb.append(name); |
| sb.append("' was not found, but this is likely normal since package '"); |
| sb.append(pkgName); |
| sb.append("' is optionally imported by bundle "); |
| sb.append(impId); |
| sb.append("."); |
| if (exporters.length > 0) |
| { |
| sb.append(" However, bundle "); |
| sb.append(expId); |
| if (reqs[i].isSatisfied( |
| Util.getExportPackage(exporters[0], reqs[i].getName()))) |
| { |
| sb.append(" does export this package. Bundle "); |
| sb.append(expId); |
| sb.append(" must be installed before bundle "); |
| sb.append(impId); |
| sb.append(" is resolved or else the optional import will be ignored."); |
| } |
| else |
| { |
| sb.append(" does export this package with attributes that do not match."); |
| } |
| } |
| sb.append(" ***"); |
| |
| return sb.toString(); |
| } |
| } |
| */ |
| // Next, check to see if the package is dynamically imported by the module. |
| if (resolver.isAllowedDynamicImport(module, pkgName)) |
| { |
| // Try to see if there is an exporter available. |
| List<Directive> dirs = Collections.EMPTY_LIST; |
| List<Attribute> attrs = new ArrayList(1); |
| attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false)); |
| Requirement req = new RequirementImpl( |
| module, Capability.PACKAGE_NAMESPACE, dirs, attrs); |
| Set<Capability> exporters = resolver.getCandidates(module, req, false); |
| |
| Wire wire = null; |
| try |
| { |
| wire = resolver.resolve(module, pkgName); |
| } |
| catch (Exception ex) |
| { |
| wire = null; |
| } |
| |
| String exporter = (exporters.size() == 0) |
| ? null : exporters.iterator().next().getModule().getBundle().toString(); |
| |
| StringBuffer sb = new StringBuffer("*** Class '"); |
| sb.append(name); |
| sb.append("' was not found, but this is likely normal since package '"); |
| sb.append(pkgName); |
| sb.append("' is dynamically imported by bundle "); |
| sb.append(importer); |
| sb.append("."); |
| if ((exporters.size() > 0) && (wire == null)) |
| { |
| sb.append(" However, bundle "); |
| sb.append(exporter); |
| sb.append(" does export this package with attributes that do not match."); |
| } |
| sb.append(" ***"); |
| |
| return sb.toString(); |
| } |
| |
| // Next, check to see if there are any exporters for the package at all. |
| List<Directive> dirs = Collections.EMPTY_LIST; |
| List<Attribute> attrs = new ArrayList(1); |
| attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false)); |
| Requirement req = new RequirementImpl( |
| module, Capability.PACKAGE_NAMESPACE, dirs, attrs); |
| Set<Capability> exports = resolver.getCandidates(module, req, false); |
| if (exports.size() > 0) |
| { |
| boolean classpath = false; |
| try |
| { |
| ModuleClassLoader.class.getClassLoader().loadClass(name); |
| classpath = true; |
| } |
| catch (NoClassDefFoundError err) |
| { |
| // Ignore |
| } |
| catch (Exception ex) |
| { |
| // Ignore |
| } |
| |
| String exporter = exports.iterator().next().getModule().getBundle().toString(); |
| |
| StringBuffer sb = new StringBuffer("*** Class '"); |
| sb.append(name); |
| sb.append("' was not found because bundle "); |
| sb.append(importer); |
| sb.append(" does not import '"); |
| sb.append(pkgName); |
| sb.append("' even though bundle "); |
| sb.append(exporter); |
| sb.append(" does export it."); |
| if (classpath) |
| { |
| sb.append(" Additionally, the class is also available from the system class loader. There are two fixes: 1) Add an import for '"); |
| sb.append(pkgName); |
| sb.append("' to bundle "); |
| sb.append(importer); |
| sb.append("; imports are necessary for each class directly touched by bundle code or indirectly touched, such as super classes if their methods are used. "); |
| sb.append("2) Add package '"); |
| sb.append(pkgName); |
| sb.append("' to the '"); |
| sb.append(Constants.FRAMEWORK_BOOTDELEGATION); |
| sb.append("' property; a library or VM bug can cause classes to be loaded by the wrong class loader. The first approach is preferable for preserving modularity."); |
| } |
| else |
| { |
| sb.append(" To resolve this issue, add an import for '"); |
| sb.append(pkgName); |
| sb.append("' to bundle "); |
| sb.append(importer); |
| sb.append("."); |
| } |
| sb.append(" ***"); |
| |
| return sb.toString(); |
| } |
| |
| // Next, try to see if the class is available from the system |
| // class loader. |
| try |
| { |
| ModuleClassLoader.class.getClassLoader().loadClass(name); |
| |
| StringBuffer sb = new StringBuffer("*** Package '"); |
| sb.append(pkgName); |
| sb.append("' is not imported by bundle "); |
| sb.append(importer); |
| sb.append(", nor is there any bundle that exports package '"); |
| sb.append(pkgName); |
| sb.append("'. However, the class '"); |
| sb.append(name); |
| sb.append("' is available from the system class loader. There are two fixes: 1) Add package '"); |
| sb.append(pkgName); |
| sb.append("' to the '"); |
| sb.append(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA); |
| sb.append("' property and modify bundle "); |
| sb.append(importer); |
| sb.append(" to import this package; this causes the system bundle to export class path packages. 2) Add package '"); |
| sb.append(pkgName); |
| sb.append("' to the '"); |
| sb.append(Constants.FRAMEWORK_BOOTDELEGATION); |
| sb.append("' property; a library or VM bug can cause classes to be loaded by the wrong class loader. The first approach is preferable for preserving modularity."); |
| sb.append(" ***"); |
| |
| return sb.toString(); |
| } |
| catch (Exception ex2) |
| { |
| } |
| |
| // Finally, if there are no imports or exports for the package |
| // and it is not available on the system class path, simply |
| // log a message saying so. |
| StringBuffer sb = new StringBuffer("*** Class '"); |
| sb.append(name); |
| sb.append("' was not found. Bundle "); |
| sb.append(importer); |
| sb.append(" does not import package '"); |
| sb.append(pkgName); |
| sb.append("', nor is the package exported by any other bundle or available from the system class loader."); |
| sb.append(" ***"); |
| |
| return sb.toString(); |
| } |
| |
| static class FragmentRequirement implements Requirement |
| { |
| private final Module m_owner; |
| private final Requirement m_fragmentReq; |
| |
| public FragmentRequirement(Module owner, Requirement fragmentReq) |
| { |
| m_owner = owner; |
| m_fragmentReq = fragmentReq; |
| } |
| |
| public Module getFragment() |
| { |
| return m_fragmentReq.getModule(); |
| } |
| |
| public Module getModule() |
| { |
| return m_owner; |
| } |
| |
| public String getNamespace() |
| { |
| return m_fragmentReq.getNamespace(); |
| } |
| |
| public SimpleFilter getFilter() |
| { |
| return m_fragmentReq.getFilter(); |
| } |
| |
| public boolean isOptional() |
| { |
| return m_fragmentReq.isOptional(); |
| } |
| |
| public Directive getDirective(String name) |
| { |
| return m_fragmentReq.getDirective(name); |
| } |
| |
| public List<Directive> getDirectives() |
| { |
| return m_fragmentReq.getDirectives(); |
| } |
| |
| public String toString() |
| { |
| return m_fragmentReq.toString(); |
| } |
| } |
| } |