001/*
002  Copyright 2010-2016 Boxfuse GmbH
003  <p/>
004  Licensed under the Apache License, Version 2.0 (the "License");
005  you may not use this file except in compliance with the License.
006  You may obtain a copy of the License at
007  <p/>
008  http://www.apache.org/licenses/LICENSE-2.0
009  <p/>
010  Unless required by applicable law or agreed to in writing, software
011  distributed under the License is distributed on an "AS IS" BASIS,
012  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  See the License for the specific language governing permissions and
014  limitations under the License.
015 */
016package io.avaje.classpath.scanner.internal.scanner.classpath;
017
018import org.osgi.framework.Bundle;
019import org.osgi.framework.FrameworkUtil;
020
021import java.net.URL;
022import java.util.Enumeration;
023import java.util.Set;
024import java.util.TreeSet;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028/**
029 * OSGi specific scanner that performs the migration search in
030 * the current bundle's classpath.
031 *
032 * <p>
033 * The resources that this scanner returns can only be loaded if
034 * Flyway's ClassLoader has access to the bundle that contains the migrations.
035 * </p>
036 */
037public class OsgiClassPathLocationScanner implements ClassPathLocationScanner {
038
039  //Felix and Equinox "host" resource url pattern starts with bundleId, which is
040  // long according osgi core specification
041  private static final Pattern bundleIdPattern = Pattern.compile("^\\d+");
042
043  public Set<String> findResourceNames(String location, URL locationUrl) {
044    Set<String> resourceNames = new TreeSet<>();
045
046    Bundle bundle = targetBundleOrCurrent(FrameworkUtil.getBundle(getClass()), locationUrl);
047    @SuppressWarnings({"unchecked"})
048    Enumeration<URL> entries = bundle.findEntries(locationUrl.getPath(), "*", true);
049    if (entries != null) {
050      while (entries.hasMoreElements()) {
051        resourceNames.add(pathWithoutLeadingSlash(entries.nextElement()));
052      }
053    }
054    return resourceNames;
055  }
056
057  private Bundle targetBundleOrCurrent(Bundle currentBundle, URL locationUrl) {
058    try {
059      Bundle targetBundle = currentBundle.getBundleContext().getBundle(bundleId(locationUrl.getHost()));
060      return targetBundle != null ? targetBundle : currentBundle;
061    } catch (Exception e) {
062      return currentBundle;
063    }
064  }
065
066  private long bundleId(String host) {
067    final Matcher matcher = bundleIdPattern.matcher(host);
068    if (matcher.find()) {
069      return Double.valueOf(matcher.group()).longValue();
070    }
071    throw new IllegalArgumentException("There's no bundleId in passed URL");
072  }
073
074  private String pathWithoutLeadingSlash(URL entry) {
075    final String path = entry.getPath();
076    return path.startsWith("/") ? path.substring(1) : path;
077  }
078}