blob: 5ccaecd5dfac2f6d4aaafbfe1207bbea52cf484c [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.fileinstall.internal;
import java.io.File;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.felix.fileinstall.ArtifactListener;
import org.apache.felix.fileinstall.ArtifactTransformer;
import org.apache.felix.fileinstall.ArtifactUrlTransformer;
import org.apache.felix.fileinstall.internal.Util.Logger;
import org.apache.felix.utils.properties.InterpolationHelper;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* This clever little bundle watches a directory and will install any jar file
* if finds in that directory (as long as it is a valid bundle and not a
* fragment).
*
*/
public class FileInstall implements BundleActivator, ServiceTrackerCustomizer
{
Runnable cmSupport;
final Map<ServiceReference, ArtifactListener> listeners = new TreeMap<ServiceReference, ArtifactListener>();
final BundleTransformer bundleTransformer = new BundleTransformer();
BundleContext context;
final Map<String, DirectoryWatcher> watchers = new HashMap<String, DirectoryWatcher>();
ServiceTracker listenersTracker;
final ReadWriteLock lock = new ReentrantReadWriteLock();
ServiceRegistration urlHandlerRegistration;
volatile boolean stopped;
public void start(BundleContext context) throws Exception
{
this.context = context;
lock.writeLock().lock();
try
{
Hashtable<String, Object> props = new Hashtable<String, Object>();
props.put("url.handler.protocol", JarDirUrlHandler.PROTOCOL);
urlHandlerRegistration = context.registerService(org.osgi.service.url.URLStreamHandlerService.class.getName(), new JarDirUrlHandler(), props);
String flt = "(|(" + Constants.OBJECTCLASS + "=" + ArtifactInstaller.class.getName() + ")"
+ "(" + Constants.OBJECTCLASS + "=" + ArtifactTransformer.class.getName() + ")"
+ "(" + Constants.OBJECTCLASS + "=" + ArtifactUrlTransformer.class.getName() + "))";
listenersTracker = new ServiceTracker(context, FrameworkUtil.createFilter(flt), this);
listenersTracker.open();
try
{
cmSupport = new ConfigAdminSupport(context, this);
}
catch (NoClassDefFoundError e)
{
Util.log(context, Logger.LOG_DEBUG,
"ConfigAdmin is not available, some features will be disabled", e);
}
// Created the initial configuration
Hashtable<String, String> ht = new Hashtable<String, String>();
set(ht, DirectoryWatcher.POLL);
set(ht, DirectoryWatcher.DIR);
set(ht, DirectoryWatcher.LOG_LEVEL);
set(ht, DirectoryWatcher.LOG_DEFAULT);
set(ht, DirectoryWatcher.FILTER);
set(ht, DirectoryWatcher.TMPDIR);
set(ht, DirectoryWatcher.START_NEW_BUNDLES);
set(ht, DirectoryWatcher.USE_START_TRANSIENT);
set(ht, DirectoryWatcher.USE_START_ACTIVATION_POLICY);
set(ht, DirectoryWatcher.NO_INITIAL_DELAY);
set(ht, DirectoryWatcher.DISABLE_CONFIG_SAVE);
set(ht, DirectoryWatcher.ENABLE_CONFIG_SAVE);
set(ht, DirectoryWatcher.CONFIG_ENCODING);
set(ht, DirectoryWatcher.START_LEVEL);
set(ht, DirectoryWatcher.ACTIVE_LEVEL);
set(ht, DirectoryWatcher.UPDATE_WITH_LISTENERS);
set(ht, DirectoryWatcher.OPTIONAL_SCOPE);
set(ht, DirectoryWatcher.FRAGMENT_SCOPE);
set(ht, DirectoryWatcher.DISABLE_NIO2);
set(ht, DirectoryWatcher.SUBDIR_MODE);
// check if dir is an array of dirs
String dirs = ht.get(DirectoryWatcher.DIR);
if (dirs != null && dirs.indexOf(',') != -1)
{
StringTokenizer st = new StringTokenizer(dirs, ",");
int index = 0;
while (st.hasMoreTokens())
{
final String dir = st.nextToken().trim();
ht.put(DirectoryWatcher.DIR, dir);
String name = "initial";
if (index > 0) name = name + index;
updated(name, new Hashtable<String, String>(ht));
index++;
}
}
else
{
updated("initial", ht);
}
}
finally
{
// now notify all the directory watchers to proceed
// We need this to avoid race conditions observed in FELIX-2791
lock.writeLock().unlock();
}
}
public Object addingService(ServiceReference serviceReference)
{
ArtifactListener listener = (ArtifactListener) context.getService(serviceReference);
addListener(serviceReference, listener);
return listener;
}
public void modifiedService(ServiceReference reference, Object service)
{
removeListener(reference, (ArtifactListener) service);
addListener(reference, (ArtifactListener) service);
}
public void removedService(ServiceReference serviceReference, Object service)
{
removeListener(serviceReference, (ArtifactListener) service);
}
// Adapted for FELIX-524
private void set(Hashtable<String, String> ht, String key)
{
String o = context.getProperty(key);
if (o == null)
{
o = System.getProperty(key.toUpperCase().replace('.', '_'));
if (o == null)
{
return;
}
}
ht.put(key, o);
}
public void stop(BundleContext context) throws Exception
{
lock.writeLock().lock();
try
{
urlHandlerRegistration.unregister();
List<DirectoryWatcher> toClose = new ArrayList<DirectoryWatcher>();
synchronized (watchers)
{
toClose.addAll(watchers.values());
watchers.clear();
}
for (DirectoryWatcher aToClose : toClose)
{
try
{
aToClose.close();
}
catch (Exception e)
{
// Ignore
}
}
if (listenersTracker != null)
{
listenersTracker.close();
}
if (cmSupport != null)
{
cmSupport.run();
}
}
finally
{
stopped = true;
lock.writeLock().unlock();
}
}
public void deleted(String pid)
{
DirectoryWatcher watcher;
synchronized (watchers)
{
watcher = watchers.remove(pid);
}
if (watcher != null)
{
watcher.close();
}
}
public void updated(String pid, Map<String, String> properties)
{
InterpolationHelper.performSubstitution(properties, context);
DirectoryWatcher watcher;
synchronized (watchers)
{
watcher = watchers.get(pid);
if (watcher != null && watcher.getProperties().equals(properties))
{
return;
}
}
if (watcher != null)
{
watcher.close();
}
watcher = new DirectoryWatcher(this, properties, context);
watcher.setDaemon(true);
synchronized (watchers)
{
watchers.put(pid, watcher);
}
watcher.start();
}
public void updateChecksum(File file)
{
List<DirectoryWatcher> toUpdate = new ArrayList<DirectoryWatcher>();
synchronized (watchers)
{
toUpdate.addAll(watchers.values());
}
for (DirectoryWatcher watcher : toUpdate)
{
watcher.scanner.updateChecksum(file);
}
}
private void addListener(ServiceReference reference, ArtifactListener listener)
{
synchronized (listeners)
{
listeners.put(reference, listener);
}
long currentStamp = reference.getBundle().getLastModified();
List<DirectoryWatcher> toNotify = new ArrayList<DirectoryWatcher>();
synchronized (watchers)
{
toNotify.addAll(watchers.values());
}
for (DirectoryWatcher dir : toNotify)
{
dir.addListener(listener, currentStamp);
}
}
private void removeListener(ServiceReference reference, ArtifactListener listener)
{
synchronized (listeners)
{
listeners.remove(reference);
}
List<DirectoryWatcher> toNotify = new ArrayList<DirectoryWatcher>();
synchronized (watchers)
{
toNotify.addAll(watchers.values());
}
for (DirectoryWatcher dir : toNotify)
{
dir.removeListener(listener);
}
}
List<ArtifactListener> getListeners()
{
synchronized (listeners)
{
List<ArtifactListener> l = new ArrayList<ArtifactListener>(listeners.values());
Collections.reverse(l);
l.add(bundleTransformer);
return l;
}
}
/**
* Convenience to refresh the packages
*/
static void refresh(Bundle systemBundle, Collection<Bundle> bundles) throws InterruptedException
{
final CountDownLatch latch = new CountDownLatch(1);
FrameworkWiring wiring = systemBundle.adapt(FrameworkWiring.class);
wiring.refreshBundles(bundles, new FrameworkListener() {
public void frameworkEvent(FrameworkEvent event) {
latch.countDown();
}
});
latch.await();
}
private class ConfigAdminSupport implements Runnable
{
private Tracker tracker;
private ServiceRegistration registration;
private ConfigAdminSupport(BundleContext context, FileInstall fileInstall)
{
tracker = new Tracker(context, fileInstall);
Hashtable<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_PID, tracker.getName());
registration = context.registerService(ManagedServiceFactory.class.getName(), tracker, props);
tracker.open();
}
public void run()
{
tracker.close();
registration.unregister();
}
private class Tracker extends ServiceTracker<ConfigurationAdmin, ConfigurationAdmin> implements ManagedServiceFactory {
private final FileInstall fileInstall;
private final Set<String> configs = Collections.synchronizedSet(new HashSet<String>());
private final Map<Long, ConfigInstaller> configInstallers = new HashMap<Long, ConfigInstaller>();
private Tracker(BundleContext bundleContext, FileInstall fileInstall)
{
super(bundleContext, ConfigurationAdmin.class.getName(), null);
this.fileInstall = fileInstall;
}
public String getName()
{
return "org.apache.felix.fileinstall";
}
public void updated(String s, Dictionary<String, ?> dictionary) throws ConfigurationException
{
configs.add(s);
Map<String, String> props = new HashMap<String, String>();
for (Enumeration<String> e = dictionary.keys(); e.hasMoreElements();) {
String k = e.nextElement();
props.put(k, dictionary.get(k).toString());
}
fileInstall.updated(s, props);
}
public void deleted(String s)
{
configs.remove(s);
fileInstall.deleted(s);
}
public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> serviceReference)
{
lock.writeLock().lock();
try
{
if (stopped) {
return null;
}
ConfigurationAdmin cm = super.addingService(serviceReference);
long id = (Long) serviceReference.getProperty(Constants.SERVICE_ID);
ConfigInstaller configInstaller = new ConfigInstaller(this.context, cm, fileInstall);
configInstaller.init();
configInstallers.put(id, configInstaller);
return cm;
}
finally
{
lock.writeLock().unlock();
}
}
public void removedService(ServiceReference<ConfigurationAdmin> serviceReference, ConfigurationAdmin o)
{
lock.writeLock().lock();
try
{
if (stopped) {
return;
}
Iterator iterator = configs.iterator();
while (iterator.hasNext())
{
String s = (String) iterator.next();
fileInstall.deleted(s);
iterator.remove();
}
long id = (Long) serviceReference.getProperty(Constants.SERVICE_ID);
ConfigInstaller configInstaller = configInstallers.remove(id);
if (configInstaller != null)
{
configInstaller.destroy();
}
super.removedService(serviceReference, o);
}
finally
{
lock.writeLock().unlock();
}
}
}
}
}