| /* |
| * 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.connect; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.StringTokenizer; |
| |
| import org.apache.felix.connect.felix.framework.DTOFactory; |
| import org.apache.felix.connect.felix.framework.ServiceRegistry; |
| import org.apache.felix.connect.felix.framework.util.EventDispatcher; |
| import org.apache.felix.connect.felix.framework.util.MapToDictionary; |
| import org.apache.felix.connect.felix.framework.util.StringMap; |
| import org.osgi.dto.DTO; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleActivator; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleEvent; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.startlevel.BundleStartLevel; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRequirement; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleRevisions; |
| import org.osgi.framework.wiring.BundleWire; |
| import org.osgi.framework.wiring.BundleWiring; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| import org.osgi.resource.Wire; |
| |
| class PojoSRBundle implements Bundle, BundleRevisions |
| { |
| private final Revision m_revision; |
| private final Map<String, String> m_headers; |
| private final Version m_version; |
| private final String m_location; |
| private final Map<Long, Bundle> m_bundles; |
| private final ServiceRegistry m_registry; |
| private final String m_activatorClass; |
| private final long m_id; |
| private final String m_symbolicName; |
| private volatile BundleActivator m_activator = null; |
| volatile int m_state = Bundle.RESOLVED; |
| volatile BundleContext m_context = null; |
| private final EventDispatcher m_dispatcher; |
| private final ClassLoader m_classLoader; |
| private final Map<Class, Object> m_services; |
| private final Map m_config; |
| |
| public PojoSRBundle(ServiceRegistry registry, |
| EventDispatcher dispatcher, |
| Map<Long, Bundle> bundles, |
| String location, |
| long id, |
| String symbolicName, |
| Version version, |
| Revision revision, |
| ClassLoader classLoader, |
| Map<String, String> headers, |
| Map<Class, Object> services, |
| Map<? extends Object, ? extends Object> config) |
| { |
| m_revision = revision; |
| m_headers = headers; |
| m_version = version; |
| m_location = location; |
| m_registry = registry; |
| m_dispatcher = dispatcher; |
| m_activatorClass = headers.get(Constants.BUNDLE_ACTIVATOR); |
| m_id = id; |
| m_symbolicName = symbolicName; |
| m_bundles = bundles; |
| m_classLoader = classLoader; |
| m_services = services; |
| m_config = config; |
| if (classLoader instanceof BundleAware) { |
| ((BundleAware) classLoader).setBundle(this); |
| } |
| if (services != null) { |
| for (Object o : services.values()) { |
| if (o instanceof BundleAware) { |
| ((BundleAware) o).setBundle(this); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public int getState() |
| { |
| return m_state; |
| } |
| |
| @Override |
| public void start(int options) throws BundleException |
| { |
| // TODO: lifecycle - fix this |
| start(); |
| } |
| |
| @Override |
| public synchronized void start() throws BundleException |
| { |
| if (m_state != Bundle.RESOLVED) |
| { |
| if (m_state == Bundle.ACTIVE) |
| { |
| return; |
| } |
| throw new BundleException("Bundle is in wrong state for start"); |
| } |
| try |
| { |
| m_state = Bundle.STARTING; |
| |
| m_context = new PojoSRBundleContext(this, m_registry, m_dispatcher, m_bundles, m_config); |
| m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTING, this)); |
| if (m_activatorClass != null) |
| { |
| m_activator = (BundleActivator) m_classLoader.loadClass(m_activatorClass).newInstance(); |
| m_activator.start(m_context); |
| } |
| m_state = Bundle.ACTIVE; |
| m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTED, this)); |
| } |
| catch (Throwable ex) |
| { |
| m_state = Bundle.RESOLVED; |
| m_activator = null; |
| m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPED, this)); |
| throw new BundleException("Unable to start bundle", ex); |
| } |
| } |
| |
| @Override |
| public void stop(int options) throws BundleException |
| { |
| // TODO: lifecycle - fix this |
| stop(); |
| } |
| |
| @Override |
| public synchronized void stop() throws BundleException |
| { |
| if (m_state != Bundle.ACTIVE) |
| { |
| if (m_state == Bundle.RESOLVED) |
| { |
| return; |
| } |
| throw new BundleException("Bundle is in wrong state for stop"); |
| } |
| try |
| { |
| m_state = Bundle.STOPPING; |
| m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPING, |
| this)); |
| if (m_activator != null) |
| { |
| m_activator.stop(m_context); |
| } |
| } |
| catch (Throwable ex) |
| { |
| throw new BundleException("Error while stopping bundle", ex); |
| } |
| finally |
| { |
| m_registry.unregisterServices(this); |
| m_dispatcher.removeListeners(m_context); |
| m_activator = null; |
| m_context = null; |
| m_state = Bundle.RESOLVED; |
| m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPED, |
| this)); |
| } |
| } |
| |
| @Override |
| public void update(InputStream input) throws BundleException |
| { |
| throw new BundleException("pojosr bundles can't be updated"); |
| } |
| |
| @Override |
| public void update() throws BundleException |
| { |
| throw new BundleException("pojosr bundles can't be updated"); |
| } |
| |
| @Override |
| public void uninstall() throws BundleException |
| { |
| throw new BundleException("pojosr bundles can't be uninstalled"); |
| } |
| |
| @Override |
| public Dictionary<String, String> getHeaders() |
| { |
| return getHeaders(Locale.getDefault().toString()); |
| } |
| |
| @Override |
| public long getBundleId() |
| { |
| return m_id; |
| } |
| |
| @Override |
| public String getLocation() |
| { |
| return m_location; |
| } |
| |
| @Override |
| public ServiceReference<?>[] getRegisteredServices() |
| { |
| return m_registry.getRegisteredServices(this); |
| } |
| |
| @Override |
| public ServiceReference<?>[] getServicesInUse() |
| { |
| return m_registry.getServicesInUse(this); |
| } |
| |
| @Override |
| public boolean hasPermission(Object permission) |
| { |
| // TODO: security - fix this |
| return true; |
| } |
| |
| @Override |
| public URL getResource(String name) |
| { |
| URL result = getEntry(name); |
| return result != null ? result : m_classLoader.getResource(name != null ? name.trim().startsWith("/") ? name.substring(1) : name : null); |
| } |
| |
| @Override |
| public Dictionary<String, String> getHeaders(String locale) |
| { |
| return new MapToDictionary<String, String>(getCurrentLocalizedHeader(locale)); |
| } |
| |
| Map<String, String> getCurrentLocalizedHeader(String locale) |
| { |
| Map<String, String> result = null; |
| |
| // Spec says empty local returns raw headers. |
| if ((locale == null) || (locale.length() == 0)) |
| { |
| result = (Map) new StringMap(m_headers); |
| } |
| |
| // If we have no result, try to get it from the cached headers. |
| if (result == null) |
| { |
| synchronized (m_cachedHeaders) |
| { |
| // If the bundle is uninstalled, then the cached headers should |
| // only contain the localized headers for the default locale at |
| // the time of uninstall, so just return that. |
| if (getState() == Bundle.UNINSTALLED) |
| { |
| result = m_cachedHeaders.values().iterator().next(); |
| } |
| // If the bundle has been updated, clear the cached headers. |
| else if (getLastModified() > m_cachedHeadersTimestamp) |
| { |
| m_cachedHeaders.clear(); |
| } |
| // Otherwise, returned the cached headers if they exist. |
| else |
| { |
| // Check if headers for this locale have already been resolved |
| result = m_cachedHeaders.get(locale); |
| } |
| } |
| } |
| |
| // If the requested locale is not cached, then try to create it. |
| if (result == null) |
| { |
| // Get a modifiable copy of the raw headers. |
| Map<String, String> headers = (Map) new StringMap(m_headers); |
| // Assume for now that this will be the result. |
| result = headers; |
| |
| // Check to see if we actually need to localize anything |
| boolean localize = false; |
| for (String s : headers.values()) |
| { |
| if ((s).startsWith("%")) |
| { |
| localize = true; |
| break; |
| } |
| } |
| |
| if (!localize) |
| { |
| // If localization is not needed, just cache the headers and |
| // return |
| // them as-is. Not sure if this is useful |
| updateHeaderCache(locale, headers); |
| } |
| else |
| { |
| // Do localization here and return the localized headers |
| String basename = headers.get(Constants.BUNDLE_LOCALIZATION); |
| if (basename == null) |
| { |
| basename = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME; |
| } |
| |
| // Create ordered list of files to load properties from |
| List<String> resourceList = createLocalizationResourceList(basename, locale); |
| |
| // Create a merged props file with all available props for this |
| // locale |
| boolean found = false; |
| Properties mergedProperties = new Properties(); |
| for (String aResourceList : resourceList) |
| { |
| URL temp = m_revision.getEntry(aResourceList + ".properties"); |
| if (temp != null) |
| { |
| found = true; |
| try |
| { |
| mergedProperties.load(temp.openConnection().getInputStream()); |
| } |
| catch (IOException ex) |
| { |
| // File doesn't exist, just continue loop |
| } |
| } |
| } |
| |
| // If the specified locale was not found, then the spec says we |
| // should |
| // return the default localization. |
| if (!found && !locale.equals(Locale.getDefault().toString())) |
| { |
| result = getCurrentLocalizedHeader(Locale.getDefault().toString()); |
| } |
| // Otherwise, perform the localization based on the discovered |
| // properties and cache the result. |
| else |
| { |
| // Resolve all localized header entries |
| for (Map.Entry<String, String> entry : headers.entrySet()) |
| { |
| String value = entry.getValue(); |
| if (value.startsWith("%")) |
| { |
| String newvalue; |
| String key = value.substring(value.indexOf("%") + 1); |
| newvalue = mergedProperties.getProperty(key); |
| if (newvalue == null) |
| { |
| newvalue = key; |
| } |
| entry.setValue(newvalue); |
| } |
| } |
| |
| updateHeaderCache(locale, headers); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| private void updateHeaderCache(String locale, Map<String, String> localizedHeaders) |
| { |
| synchronized (m_cachedHeaders) |
| { |
| m_cachedHeaders.put(locale, localizedHeaders); |
| m_cachedHeadersTimestamp = System.currentTimeMillis(); |
| } |
| } |
| |
| private final Map<String, Map<String, String>> m_cachedHeaders = new HashMap<String, Map<String, String>>(); |
| private long m_cachedHeadersTimestamp; |
| |
| private static List<String> createLocalizationResourceList(String basename, String locale) |
| { |
| List<String> result = new ArrayList<String>(4); |
| |
| StringTokenizer tokens; |
| StringBuilder tempLocale = new StringBuilder(basename); |
| |
| result.add(tempLocale.toString()); |
| |
| if (locale.length() > 0) |
| { |
| tokens = new StringTokenizer(locale, "_"); |
| while (tokens.hasMoreTokens()) |
| { |
| tempLocale.append("_").append(tokens.nextToken()); |
| result.add(tempLocale.toString()); |
| } |
| } |
| return result; |
| } |
| |
| public String getSymbolicName() |
| { |
| return m_symbolicName; |
| } |
| |
| public Class<?> loadClass(String name) throws ClassNotFoundException |
| { |
| return m_classLoader.loadClass(name); |
| } |
| |
| @Override |
| public Enumeration<URL> getResources(String name) throws IOException |
| { |
| // TODO: module - implement this based on the revision |
| return m_classLoader.getResources(name); |
| } |
| |
| @Override |
| public Enumeration<String> getEntryPaths(String path) |
| { |
| Enumeration<String> result = new EntryFilterEnumeration<String>(m_revision, false, path, null, false, |
| false); |
| return result.hasMoreElements() ? result : null; |
| } |
| |
| @Override |
| public URL getEntry(String path) |
| { |
| URL result = m_revision.getEntry(path); |
| return result; |
| } |
| |
| @Override |
| public long getLastModified() |
| { |
| return m_revision.getLastModified(); |
| } |
| |
| @Override |
| public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) |
| { |
| // TODO: module - implement this based on the revision |
| Enumeration<URL> result = new EntryFilterEnumeration<URL>(m_revision, true, path, filePattern, recurse, true); |
| return result.hasMoreElements() ? result : null; |
| } |
| |
| @Override |
| public BundleContext getBundleContext() |
| { |
| return m_context; |
| } |
| |
| @Override |
| public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) |
| { |
| // TODO: security - fix this |
| return new HashMap<X509Certificate, List<X509Certificate>>(); |
| } |
| |
| @Override |
| public Version getVersion() |
| { |
| return m_version; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (o instanceof PojoSRBundle) |
| { |
| return ((PojoSRBundle) o).m_id == m_id; |
| } |
| return false; |
| } |
| |
| @Override |
| public int compareTo(Bundle o) |
| { |
| long thisBundleId = this.getBundleId(); |
| long thatBundleId = o.getBundleId(); |
| return (thisBundleId < thatBundleId ? -1 : (thisBundleId == thatBundleId ? 0 : 1)); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <A> A adapt(Class<A> type) |
| { |
| if (m_services != null && m_services.containsKey(type)) |
| { |
| return (A) m_services.get(type); |
| } |
| if (type.isInstance(this)) |
| { |
| return (A) this; |
| } |
| if (type == BundleWiring.class) |
| { |
| return (A) new BundleWiringImpl(this, m_classLoader); |
| } |
| if (type == BundleRevision.class) |
| { |
| return (A) new BundleRevisionImpl(this); |
| } |
| if (type == BundleStartLevel.class) |
| { |
| return (A) new BundleStartLevelImpl(this); |
| } |
| if (DTO.class.isAssignableFrom(type) || |
| DTO[].class.isAssignableFrom(type)) |
| { |
| return DTOFactory.createDTO(this, type); |
| } |
| return null; |
| } |
| |
| public File getDataFile(String filename) |
| { |
| return m_context.getDataFile(filename); |
| } |
| |
| public String toString() |
| { |
| String sym = getSymbolicName(); |
| if (sym != null) |
| { |
| return sym + " [" + getBundleId() + "]"; |
| } |
| return "[" + getBundleId() + "]"; |
| } |
| |
| @Override |
| public List<BundleRevision> getRevisions() |
| { |
| return Arrays.asList(adapt(BundleRevision.class)); |
| } |
| |
| @Override |
| public Bundle getBundle() |
| { |
| return this; |
| } |
| |
| |
| public static class BundleStartLevelImpl implements BundleStartLevel |
| { |
| private final Bundle bundle; |
| |
| public BundleStartLevelImpl(Bundle bundle) |
| { |
| this.bundle = bundle; |
| } |
| |
| public int getStartLevel() |
| { |
| // TODO Implement this? |
| return 1; |
| } |
| |
| public void setStartLevel(int startlevel) |
| { |
| // TODO Implement this? |
| } |
| |
| public boolean isPersistentlyStarted() |
| { |
| return true; |
| } |
| |
| public boolean isActivationPolicyUsed() |
| { |
| return false; |
| } |
| |
| @Override |
| public Bundle getBundle() |
| { |
| return bundle; |
| } |
| } |
| |
| public static class BundleRevisionImpl implements BundleRevision |
| { |
| private final Bundle bundle; |
| |
| public BundleRevisionImpl(Bundle bundle) |
| { |
| this.bundle = bundle; |
| } |
| |
| @Override |
| public String getSymbolicName() |
| { |
| return bundle.getSymbolicName(); |
| } |
| |
| @Override |
| public Version getVersion() |
| { |
| return bundle.getVersion(); |
| } |
| |
| @Override |
| public List<BundleCapability> getDeclaredCapabilities(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<BundleRequirement> getDeclaredRequirements(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public int getTypes() |
| { |
| if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) |
| { |
| return BundleRevision.TYPE_FRAGMENT; |
| } |
| return 0; |
| } |
| |
| @Override |
| public BundleWiring getWiring() |
| { |
| return bundle.adapt(BundleWiring.class); |
| } |
| |
| @Override |
| public List<Capability> getCapabilities(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<Requirement> getRequirements(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public Bundle getBundle() |
| { |
| return bundle; |
| } |
| } |
| |
| public static class BundleWiringImpl implements BundleWiring |
| { |
| |
| private final Bundle bundle; |
| private final ClassLoader classLoader; |
| |
| public BundleWiringImpl(Bundle bundle, ClassLoader classLoader) |
| { |
| this.bundle = bundle; |
| this.classLoader = classLoader; |
| } |
| |
| @Override |
| public boolean isInUse() |
| { |
| return true; |
| } |
| |
| @Override |
| public boolean isCurrent() |
| { |
| return true; |
| } |
| |
| @Override |
| public BundleRevision getRevision() |
| { |
| return bundle.adapt(BundleRevision.class); |
| } |
| |
| @Override |
| public List<BundleRequirement> getRequirements(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<BundleWire> getRequiredWires(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<BundleWire> getProvidedWires(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public ClassLoader getClassLoader() |
| { |
| return classLoader; |
| } |
| |
| @Override |
| public List<BundleCapability> getCapabilities(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<Capability> getResourceCapabilities(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<Requirement> getResourceRequirements(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<Wire> getProvidedResourceWires(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public List<Wire> getRequiredResourceWires(String namespace) |
| { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public BundleRevision getResource() |
| { |
| return getRevision(); |
| } |
| |
| @Override |
| public Bundle getBundle() |
| { |
| return bundle; |
| } |
| |
| @Override |
| public List<URL> findEntries(String path, String filePattern, int options) |
| { |
| List<URL> result = new ArrayList<URL>(); |
| for (Enumeration<URL> e = bundle.findEntries(path, filePattern, options == BundleWiring.FINDENTRIES_RECURSE); e.hasMoreElements(); ) |
| { |
| result.add(e.nextElement()); |
| } |
| return result; |
| } |
| |
| @Override |
| public Collection<String> listResources(String path, String filePattern, int options) |
| { |
| // TODO: this is wrong, we should return the resource names |
| Collection<String> result = new ArrayList<String>(); |
| for (URL u : findEntries(path, filePattern, options)) |
| { |
| result.add(u.toString()); |
| } |
| return result; |
| } |
| |
| } |
| |
| } |