| /* |
| * 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.net.URL; |
| import java.security.ProtectionDomain; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.felix.framework.cache.Content; |
| import org.apache.felix.framework.util.FelixConstants; |
| import org.apache.felix.framework.util.MultiReleaseContent; |
| import org.apache.felix.framework.util.SecureAction; |
| import org.apache.felix.framework.util.Util; |
| import org.apache.felix.framework.util.manifestparser.ManifestParser; |
| import org.apache.felix.framework.util.manifestparser.NativeLibrary; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRequirement; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleWiring; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| import org.osgi.resource.Resource; |
| |
| public class BundleRevisionImpl implements BundleRevision, Resource |
| { |
| public final static int EAGER_ACTIVATION = 0; |
| public final static int LAZY_ACTIVATION = 1; |
| |
| private final String m_id; |
| private final Map<String, Object> m_headerMap; |
| |
| private final String m_manifestVersion; |
| private final boolean m_isExtension; |
| private final boolean m_isFragment; |
| private final String m_symbolicName; |
| private final Version m_version; |
| |
| private final List<BundleCapability> m_declaredCaps; |
| private final List<BundleRequirement> m_declaredReqs; |
| private final List<NativeLibrary> m_declaredNativeLibs; |
| private final int m_declaredActivationPolicy; |
| private final List<String> m_activationIncludes; |
| private final List<String> m_activationExcludes; |
| |
| private final BundleImpl m_bundle; |
| |
| private volatile Content m_content; |
| private volatile List<Content> m_contentPath; |
| private volatile ProtectionDomain m_protectionDomain = null; |
| private final static SecureAction m_secureAction = new SecureAction(); |
| |
| // Bundle wiring when resolved. |
| private volatile BundleWiringImpl m_wiring = null; |
| |
| /** |
| * This constructor is used by the extension manager, since it needs |
| * a constructor that does not throw an exception. |
| * @param bundle |
| * @param id |
| * @throws org.osgi.framework.BundleException |
| */ |
| public BundleRevisionImpl(BundleImpl bundle, String id) |
| { |
| m_bundle = bundle; |
| m_id = id; |
| m_headerMap = null; |
| m_content = null; |
| m_manifestVersion = ""; |
| m_symbolicName = null; |
| m_isExtension = false; |
| m_isFragment = false; |
| m_version = null; |
| m_declaredCaps = Collections.emptyList(); |
| m_declaredReqs = Collections.emptyList(); |
| m_declaredNativeLibs = null; |
| m_declaredActivationPolicy = EAGER_ACTIVATION; |
| m_activationExcludes = null; |
| m_activationIncludes = null; |
| } |
| |
| BundleRevisionImpl( |
| BundleImpl bundle, String id, Map<String, Object> headerMap, Content content) |
| throws BundleException |
| { |
| m_bundle = bundle; |
| m_id = id; |
| m_headerMap = headerMap; |
| m_content = content; |
| |
| ManifestParser mp = new ManifestParser( |
| bundle.getFramework().getLogger(), |
| bundle.getFramework().getConfig(), |
| 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_isExtension = mp.isExtension(); |
| m_manifestVersion = mp.getManifestVersion(); |
| m_version = mp.getBundleVersion(); |
| m_declaredCaps = mp.getCapabilities(); |
| m_declaredReqs = mp.getRequirements(); |
| m_declaredNativeLibs = 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_isFragment = m_headerMap.containsKey(Constants.FRAGMENT_HOST); |
| } |
| |
| static SecureAction getSecureAction() |
| { |
| return m_secureAction; |
| } |
| |
| int getDeclaredActivationPolicy() |
| { |
| return m_declaredActivationPolicy; |
| } |
| |
| 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; |
| } |
| |
| // |
| // BundleRevision methods. |
| // |
| |
| public String getSymbolicName() |
| { |
| return m_symbolicName; |
| } |
| |
| public Version getVersion() |
| { |
| return m_version; |
| } |
| |
| public List<Capability> getCapabilities(String namespace) |
| { |
| return asCapabilityList(getDeclaredCapabilities(namespace)); |
| } |
| |
| static List<Capability> asCapabilityList(List reqs) |
| { |
| return reqs; |
| } |
| |
| public List<BundleCapability> getDeclaredCapabilities(String namespace) |
| { |
| List<BundleCapability> result = m_declaredCaps; |
| if (namespace != null) |
| { |
| result = new ArrayList<BundleCapability>(); |
| for (BundleCapability cap : m_declaredCaps) |
| { |
| if (cap.getNamespace().equals(namespace)) |
| { |
| result.add(cap); |
| } |
| } |
| } |
| return result; |
| } |
| |
| public List<Requirement> getRequirements(String namespace) |
| { |
| return asRequirementList(getDeclaredRequirements(namespace)); |
| } |
| |
| static List<Requirement> asRequirementList(List reqs) |
| { |
| return reqs; |
| } |
| |
| public List<BundleRequirement> getDeclaredRequirements(String namespace) |
| { |
| List<BundleRequirement> result = m_declaredReqs; |
| if (namespace != null) |
| { |
| result = new ArrayList<BundleRequirement>(); |
| for (BundleRequirement req : m_declaredReqs) |
| { |
| if (req.getNamespace().equals(namespace)) |
| { |
| result.add(req); |
| } |
| } |
| } |
| return result; |
| } |
| |
| public int getTypes() |
| { |
| return (getManifestVersion().equals("2") && m_isFragment) ? BundleRevision.TYPE_FRAGMENT : 0; |
| } |
| |
| public BundleWiring getWiring() |
| { |
| return m_wiring; |
| } |
| |
| public BundleImpl getBundle() |
| { |
| return m_bundle; |
| } |
| |
| // |
| // Implementating details. |
| // |
| |
| public Map<String, Object> getHeaders() |
| { |
| return m_headerMap; |
| } |
| |
| public boolean isExtension() |
| { |
| return m_isExtension; |
| } |
| |
| public String getManifestVersion() |
| { |
| return m_manifestVersion; |
| } |
| |
| public List<NativeLibrary> getDeclaredNativeLibraries() |
| { |
| return m_declaredNativeLibs; |
| } |
| |
| public String getId() |
| { |
| return m_id; |
| } |
| |
| public synchronized void resolve(BundleWiringImpl wiring) |
| { |
| if (m_wiring != null) |
| { |
| m_wiring.dispose(); |
| m_wiring = null; |
| } |
| |
| if (wiring != null) |
| { |
| // If the wiring has fragments, then close the old content path, |
| // since it'll need to be recalculated to include fragments. |
| if (!Util.getFragments(wiring).isEmpty()) |
| { |
| for (int i = 0; (m_contentPath != null) && (i < m_contentPath.size()); i++) |
| { |
| // Don't close this module's content, if it is on the content path. |
| if (m_content != m_contentPath.get(i)) |
| { |
| m_contentPath.get(i).close(); |
| } |
| } |
| m_contentPath = null; |
| } |
| |
| m_wiring = wiring; |
| } |
| } |
| |
| public void setProtectionDomain(ProtectionDomain pd) |
| { |
| m_protectionDomain = pd; |
| } |
| |
| public ProtectionDomain getProtectionDomain() |
| { |
| return m_protectionDomain; |
| } |
| |
| // |
| // Content access methods. |
| // |
| |
| public Content getContent() |
| { |
| return m_content; |
| } |
| |
| void resetContent(Content content) |
| { |
| m_content = content; |
| } |
| |
| List<Content> getContentPath() |
| { |
| if (m_contentPath == null) |
| { |
| try |
| { |
| m_contentPath = initializeContentPath(); |
| } |
| catch (Exception ex) |
| { |
| m_bundle.getFramework().getLogger().log( |
| m_bundle, Logger.LOG_ERROR, "Unable to get module class path.", ex); |
| } |
| } |
| return m_contentPath; |
| } |
| |
| private synchronized List<Content> initializeContentPath() throws Exception |
| { |
| if (m_contentPath != null) |
| { |
| return m_contentPath; |
| } |
| List<Content> contentList = new ArrayList(); |
| calculateContentPath(this, getContent(), contentList, true); |
| |
| List<BundleRevision> fragments = null; |
| List<Content> fragmentContents = null; |
| if (m_wiring != null) |
| { |
| // Get fragments and their contents from the wiring. |
| // Note that we don't use Util.getFragments() here because |
| // the wiring returns parallel arrays and the utility method |
| // doesn't necessarily return the correct order. |
| fragments = m_wiring.getFragments(); |
| fragmentContents = m_wiring.getFragmentContents(); |
| } |
| if (fragments != null) |
| { |
| for (int i = 0; i < fragments.size(); i++) |
| { |
| calculateContentPath( |
| fragments.get(i), fragmentContents.get(i), contentList, false); |
| } |
| } |
| return contentList; |
| } |
| |
| private List calculateContentPath( |
| BundleRevision revision, Content content, List<Content> contentList, |
| boolean searchFragments) |
| { |
| // 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) ((BundleRevisionImpl) revision) |
| .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(MultiReleaseContent.wrap( |
| getBundle().getFramework()._getProperty("java.specification.version"), 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. |
| List<Content> fragmentContents = (m_wiring == null) |
| ? null : m_wiring.getFragmentContents(); |
| for (int fragIdx = 0; |
| searchFragments && (embeddedContent == null) |
| && (fragmentContents != null) && (fragIdx < fragmentContents.size()); |
| fragIdx++) |
| { |
| embeddedContent = |
| fragmentContents.get(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(MultiReleaseContent.wrap( |
| getBundle().getFramework()._getProperty("java.specification.version"),embeddedContent)); |
| } |
| else |
| { |
| // TODO: FRAMEWORK - Per the spec, this should fire a FrameworkEvent.INFO event; |
| // need to create an "Eventer" class like "Logger" perhaps. |
| ((BundleImpl) m_bundle).getFramework().getLogger().log( |
| getBundle(), 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.isEmpty()) |
| { |
| localContentList.add(MultiReleaseContent.wrap( |
| getBundle().getFramework()._getProperty("java.specification.version"),content)); |
| } |
| |
| // Now add the local contents to the global content list and return it. |
| contentList.addAll(localContentList); |
| return contentList; |
| } |
| |
| 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 meaningful, 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. |
| List<Content> contentPath = getContentPath(); |
| for (int i = 0; |
| (url == null) && |
| (i < contentPath.size()); i++) |
| { |
| if (contentPath.get(i).hasEntry(name)) |
| { |
| url = createURL(i + 1, name); |
| } |
| } |
| |
| return url; |
| } |
| |
| 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 List<Content> contentPath = getContentPath(); |
| if (contentPath == null) |
| return Collections.enumeration(Collections.emptyList()); |
| |
| if (name.equals("/")) |
| { |
| for (int i = 0; i < contentPath.size(); 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.size(); i++) |
| { |
| if (contentPath.get(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 Collections.enumeration(l); |
| } |
| |
| // 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 getContent().hasEntry(urlPath); |
| } |
| return getContentPath().get(index - 1).hasEntry(urlPath); |
| } |
| |
| public InputStream getInputStream(int index, String urlPath) |
| throws IOException |
| { |
| if (urlPath.startsWith("/")) |
| { |
| urlPath = urlPath.substring(1); |
| } |
| if (index == 0) |
| { |
| return getContent().getEntryAsStream(urlPath); |
| } |
| return getContentPath().get(index - 1).getEntryAsStream(urlPath); |
| } |
| |
| public URL getLocalURL(int index, String urlPath) |
| { |
| if (urlPath.startsWith("/")) |
| { |
| urlPath = urlPath.substring(1); |
| } |
| if (index == 0) |
| { |
| return getContent().getEntryAsURL(urlPath); |
| } |
| return getContentPath().get(index - 1).getEntryAsURL(urlPath); |
| } |
| |
| private URL createURL(int port, String 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_bundle.getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_id + ":" + port + path, |
| getBundle().getFramework().getBundleStreamHandler()); |
| } |
| catch (Exception ex) |
| { |
| m_bundle.getFramework().getLogger().log( |
| m_bundle, |
| Logger.LOG_ERROR, |
| "Unable to create resource URL.", |
| ex); |
| } |
| return null; |
| } |
| |
| synchronized void close() |
| { |
| try |
| { |
| resolve(null); |
| } |
| catch (Exception ex) |
| { |
| ((BundleImpl) m_bundle).getFramework().getLogger().log( |
| Logger.LOG_ERROR, "Error releasing revision: " + ex.getMessage(), ex); |
| } |
| m_content.close(); |
| m_content = null; |
| for (int i = 0; (m_contentPath != null) && (i < m_contentPath.size()); i++) |
| { |
| m_contentPath.get(i).close(); |
| } |
| m_contentPath = null; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return m_bundle.toString() + "(R " + m_id + ")"; |
| } |
| } |