| /* |
| * 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(); |
| } |
| } |
| } |
| } |
| |
| } |