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