blob: b0aab9964b89d924c8dd629151f4f9f25e9e2ecf [file] [log] [blame]
/*
* 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;
}
}
}