blob: e7b492dc1def3a901d5cfcb5d92c0ac37c8b43ab [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.karaf.shell.dev.watch;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.ops4j.pax.url.maven.commons.MavenConfiguration;
import org.ops4j.pax.url.maven.commons.MavenConfigurationImpl;
import org.ops4j.pax.url.maven.commons.MavenRepositoryURL;
import org.ops4j.pax.url.mvn.ServiceConstants;
import org.ops4j.pax.url.mvn.internal.Parser;
import org.ops4j.util.property.DictionaryPropertyResolver;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.packageadmin.PackageAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Runnable singleton which watches at the defined location for bundle updates.
*/
public class BundleWatcher implements Runnable, BundleListener {
private final Logger logger = LoggerFactory.getLogger(BundleWatcher.class);
private BundleContext bundleContext;
private ConfigurationAdmin configurationAdmin;
private AtomicBoolean running = new AtomicBoolean(false);
private long interval = 1000L;
private List<String> watchURLs = new CopyOnWriteArrayList<String>();
private AtomicInteger counter = new AtomicInteger(0);
/**
* Constructor
*/
public BundleWatcher() {
}
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.INSTALLED
|| event.getType() == BundleEvent.UNINSTALLED) {
counter.incrementAndGet();
}
}
public void run() {
logger.debug("Bundle watcher thread started");
int oldCounter = -1;
Set<Bundle> watchedBundles = new HashSet<Bundle>();
while (running.get() && watchURLs.size()>0) {
if (oldCounter != counter.get()) {
oldCounter = counter.get();
watchedBundles.clear();
for (String bundleURL : watchURLs) {
for (Bundle bundle : getBundlesByURL(bundleURL)) {
watchedBundles.add(bundle);
}
}
}
if (watchedBundles.size()>0) {
File localRepository = getLocalRepository();
List<Bundle> updated = new ArrayList<Bundle>();
for (Bundle bundle : watchedBundles) {
try {
File location = getBundleExternalLocation(localRepository, bundle);
if (location != null
&& location.exists()
&& location.lastModified() > bundle.getLastModified())
{
InputStream is = new FileInputStream(location);
try {
logger.info("[Watch] Updating watched bundle: " + bundle.getSymbolicName() + " (" + bundle.getVersion() + ")");
System.out.println("[Watch] Updating watched bundle: " + bundle.getSymbolicName() + " (" + bundle.getVersion() + ")");
bundle.update(is);
updated.add(bundle);
} finally {
is.close();
}
}
} catch (IOException ex) {
logger.error("Error watching bundle.", ex);
} catch (BundleException ex) {
logger.error("Error updating bundle.", ex);
}
}
ServiceReference ref = null;
try {
ref = getBundleContext().getServiceReference(PackageAdmin.class.getName());
PackageAdmin pa = (PackageAdmin) getBundleContext().getService(ref);
pa.refreshPackages(updated.toArray(new Bundle[updated.size()]));
} finally {
if (ref != null) {
getBundleContext().ungetService(ref);
}
}
}
try {
Thread.sleep(interval);
} catch (InterruptedException ex) {
running.set(false);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Bundle watcher thread stopped");
}
}
/**
* Adds a Bundle URLs to the watch list.
* @param url
*/
public void add(String url) {
boolean shouldStart = running.get() && (watchURLs.size()==0);
if (!watchURLs.contains(url)) {
watchURLs.add(url);
counter.incrementAndGet();
}
if (shouldStart) {
Thread thread = new Thread(this);
thread.start();
}
}
/**
* Removes a bundle URLs from the watch list.
* @param url
*/
public void remove(String url) {
watchURLs.remove(url);
counter.incrementAndGet();
}
/**
* Returns the location of the Bundle inside the local maven repository.
* @param bundle
* @return
*/
public File getBundleExternalLocation(File localRepository, Bundle bundle) {
try {
Parser p = new Parser(bundle.getLocation().substring(4));
return new File(localRepository.getPath() + File.separator + p.getArtifactPath());
} catch (MalformedURLException e) {
logger.error("Could not parse artifact path for bundle" + bundle.getSymbolicName(), e);
}
return null;
}
public File getLocalRepository() {
// Attempt to retrieve local repository location from MavenConfiguration
MavenConfiguration configuration = retrieveMavenConfiguration();
if (configuration != null) {
MavenRepositoryURL localRepositoryURL = configuration.getLocalRepository();
if (localRepositoryURL != null) {
return localRepositoryURL.getFile().getAbsoluteFile();
}
}
// If local repository not found assume default.
String localRepo = System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository";
return new File(localRepo).getAbsoluteFile();
}
protected MavenConfiguration retrieveMavenConfiguration() {
MavenConfiguration mavenConfiguration = null;
try {
Configuration configuration = configurationAdmin.getConfiguration(ServiceConstants.PID);
if (configuration != null) {
Dictionary dictonary = configuration.getProperties();
if (dictonary != null) {
DictionaryPropertyResolver resolver = new DictionaryPropertyResolver(dictonary);
mavenConfiguration = new MavenConfigurationImpl(resolver, ServiceConstants.PID);
}
}
} catch (IOException e) {
logger.error("Error retrieving maven configuration",e);
}
return mavenConfiguration;
}
/**
* Returns the bundles that match
* @param url
* @return
*/
public List<Bundle> getBundlesByURL(String url) {
List<Bundle> bundleList = new ArrayList<Bundle>();
try {
Long id = Long.parseLong(url);
Bundle bundle = bundleContext.getBundle(id);
if (bundle != null) {
bundleList.add(bundle);
}
} catch (NumberFormatException e) {
for (int i = 0; i < bundleContext.getBundles().length; i++) {
Bundle bundle = bundleContext.getBundles()[i];
if (isMavenSnapshotUrl(bundle.getLocation()) && wildCardMatch(bundle.getLocation(), url)) {
bundleList.add(bundle);
}
}
}
return bundleList;
}
protected boolean isMavenSnapshotUrl(String url) {
return url.startsWith("mvn:") && url.contains("SNAPSHOT");
}
/**
* Matches text using a pattern containing wildcards.
*
* @param text
* @param pattern
* @return
*/
protected boolean wildCardMatch(String text, String pattern) {
String[] cards = pattern.split("\\*");
// Iterate over the cards.
for (String card : cards) {
int idx = text.indexOf(card);
// Card not detected in the text.
if (idx == -1) {
return false;
}
// Move ahead, towards the right of the text.
text = text.substring(idx + card.length());
}
return true;
}
public void start() {
// register the bundle listener
bundleContext.addBundleListener(this);
// start the watch thread
if (running.compareAndSet(false, true)) {
if (watchURLs.size()>0) {
Thread thread = new Thread(this);
thread.start();
}
}
}
/**
* Stops the execution of the thread and releases the singleton instance
*/
public void stop() {
running.set(false);
// unregister the bundle listener
bundleContext.removeBundleListener(this);
}
public ConfigurationAdmin getConfigurationAdmin() {
return configurationAdmin;
}
public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
this.configurationAdmin = configurationAdmin;
}
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public List<String> getWatchURLs() {
return watchURLs;
}
public void setWatchURLs(List<String> watchURLs) {
this.watchURLs = watchURLs;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
public boolean isRunning() {
return running.get();
}
}