blob: 1e38bd1e87b4cf8a64ce9604a4d4a0f882f5959a [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.bundlerepository.impl;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.felix.utils.log.Logger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.apache.felix.bundlerepository.*;
import org.osgi.service.url.AbstractURLStreamHandlerService;
/**
* Simple {@link URLStreamHandler} which is able to handle
* obr urls. The urls must be conform the following schema:
*
* obr://<symbolicName>/<timeStamp>
*
* Example:
*
* obr://org.apache.felix.javax.servlet/1240305961998
*
*
* Update to the bundle is done
*
*/
public class ObrURLStreamHandlerService extends AbstractURLStreamHandlerService
{
/**
* Syntax for the url; to be shown on exception messages.
*/
private static final String SYNTAX = "obr:<bundle-symbolic-name>['/'<bundle-version>]";
/**
* Property defining the obr update strategy
*/
public static final String OBR_UPDATE_STRATEGY = "obr.update.strategy";
/**
* The BundleContext to search for the bundles.
*/
private final BundleContext m_bundleContext;
/**
* The RepositoryAdmin to query for the actual url
* for a bundle.
*/
private final RepositoryAdmin m_reRepositoryAdmin;
/**
* Logger to use.
*/
private final Logger m_logger;
/**
* The update strategy to use.
* Default: newest
*/
private String m_updateStrategy = "newest";
/**
* Constructor
*
* @param context context to use
* @param admin admin to use
*/
public ObrURLStreamHandlerService(BundleContext context, org.apache.felix.bundlerepository.RepositoryAdmin admin)
{
m_bundleContext = context;
m_reRepositoryAdmin = admin;
m_logger = new Logger(context);
if (m_bundleContext.getProperty(OBR_UPDATE_STRATEGY) != null)
{
this.m_updateStrategy = m_bundleContext.getProperty(OBR_UPDATE_STRATEGY);
}
}
/**
* {@inheritDoc}
*
* This implementation looks up the bundle with the given
* url set as location String within the current {@link BundleContext}.
* The real url for this bundle is determined afterwards via the
* {@link RepositoryAdmin}.
*/
public URLConnection openConnection(URL u) throws IOException
{
String url = u.toExternalForm();
URL remoteURL = null;
try
{
Bundle[] bundles = m_bundleContext.getBundles();
int i = 0;
while ((remoteURL == null) && (i < bundles.length))
{
if (url.equals(bundles[i].getLocation()))
{
remoteURL = getRemoteUrlForBundle(bundles[i]);
}
i++;
}
if (remoteURL == null)
{
String path = u.getPath();
remoteURL = getRemoteObrInstallUrl(path);
}
}
catch (InvalidSyntaxException e)
{
throw (IOException) new IOException().initCause(e);
}
return remoteURL.openConnection();
}
/**
* Assume the URL is a query URL and try to find a matching resource.
*
* Note: the code from the below method comes from OPS4j Pax URL handler
*
* @param path the OBR url path
* @return the remote URL of the resolved bundle
* @throws IOException if an error occurs
*/
private URL getRemoteObrInstallUrl(String path) throws IOException, InvalidSyntaxException
{
if( path == null || path.trim().length() == 0 )
{
throw new MalformedURLException( "Path cannot be null or empty. Syntax " + SYNTAX );
}
final String[] segments = path.split( "/" );
if( segments.length > 2 )
{
throw new MalformedURLException( "Path cannot contain more then one '/'. Syntax " + SYNTAX );
}
final StringBuffer buffer = new StringBuffer();
// add bundle symbolic name filter
buffer.append( "(symbolicname=" ).append( segments[ 0 ] ).append( ")" );
if( !validateFilter( buffer.toString() ) )
{
throw new MalformedURLException( "Invalid symbolic name value." );
}
// add bundle version filter
if( segments.length > 1 )
{
buffer.insert( 0, "(&" ).append( "(version=" ).append( segments[ 1 ] ).append( "))" );
if( !validateFilter( buffer.toString() ) )
{
throw new MalformedURLException( "Invalid version value." );
}
}
Resource[] discoverResources =
m_reRepositoryAdmin.discoverResources(buffer.toString());
if (discoverResources == null || discoverResources.length == 0)
{
throw new IOException( "No resource found for filter [" + buffer.toString() + "]" );
}
ResourceSelectionStrategy strategy = new NewestSelectionStrategy(m_logger);
Resource selected = strategy.selectOne(Version.emptyVersion, discoverResources);
return new URL(selected.getURI());
}
private boolean validateFilter(String filter) {
try
{
FrameworkUtil.createFilter(filter);
return true;
}
catch (InvalidSyntaxException e)
{
return false;
}
}
/**
* Determines the remote url for the given bundle according to
* the configured {@link ResourceSelectionStrategy}.
*
* @param bundle bundle
* @return remote url
* @throws IOException if something went wrong
*/
private URL getRemoteUrlForBundle(Bundle bundle) throws IOException, InvalidSyntaxException
{
String symbolicName = bundle.getSymbolicName();
String version = (String) bundle.getHeaders().get(Constants.BUNDLE_VERSION);
StringBuffer buffer = new StringBuffer();
buffer.append("(symbolicname=");
buffer.append(symbolicName);
buffer.append(")");
Resource[] discoverResources =
m_reRepositoryAdmin.discoverResources(buffer.toString());
if (discoverResources == null || discoverResources.length == 0)
{
throw new IOException( "No resource found for filter [" + buffer.toString() + "]" );
}
ResourceSelectionStrategy strategy = getStrategy(m_updateStrategy);
Resource selected = strategy.selectOne(
Version.parseVersion(version), discoverResources);
return new URL(selected.getURI());
}
private ResourceSelectionStrategy getStrategy(String strategy)
{
m_logger.log(Logger.LOG_DEBUG, "Using ResourceSelectionStrategy: " + strategy);
if ("same".equals(strategy))
{
return new SameSelectionStrategy(m_logger);
}
else if ("newest".equals(strategy))
{
return new NewestSelectionStrategy(m_logger);
}
throw new RuntimeException("Could not determine obr update strategy : " + strategy);
}
/**
* Abstract class for Resource Selection Strategies
*/
private static abstract class ResourceSelectionStrategy
{
private final Logger m_logger;
ResourceSelectionStrategy(Logger logger)
{
m_logger = logger;
}
Logger getLogger()
{
return m_logger;
}
final Resource selectOne(Version currentVersion, Resource[] resources)
{
SortedMap sortedResources = new TreeMap();
for (int i = 0; i < resources.length; i++)
{
sortedResources.put(resources[i].getVersion(), resources[i]);
}
Version versionToUse = determineVersion(currentVersion, sortedResources);
m_logger.log(Logger.LOG_DEBUG,
"Using Version " + versionToUse + " for bundle "
+ resources[0].getSymbolicName());
return (Resource) sortedResources.get(versionToUse);
}
abstract Version determineVersion(Version currentVersion, SortedMap sortedResources);
}
/**
* Strategy returning the current version.
*/
static class SameSelectionStrategy extends ResourceSelectionStrategy
{
SameSelectionStrategy(Logger logger)
{
super(logger);
}
/**
* {@inheritDoc}
*/
Version determineVersion(Version currentVersion, SortedMap sortedResources)
{
return currentVersion;
}
}
/**
* Strategy returning the newest entry.
*/
static class NewestSelectionStrategy extends ResourceSelectionStrategy
{
NewestSelectionStrategy(Logger logger)
{
super(logger);
}
/**
* {@inheritDoc}
*/
Version determineVersion(Version currentVersion, SortedMap sortedResources)
{
return (Version) sortedResources.lastKey();
}
}
}