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 = getTargetBundleOrCurrent(FrameworkUtil.getBundle(getClass()), locationUrl);
047    @SuppressWarnings({"unchecked"})
048    Enumeration<URL> entries = bundle.findEntries(locationUrl.getPath(), "*", true);
049
050    if (entries != null) {
051      while (entries.hasMoreElements()) {
052        URL entry = entries.nextElement();
053        String resourceName = getPathWithoutLeadingSlash(entry);
054
055        resourceNames.add(resourceName);
056      }
057    }
058
059    return resourceNames;
060  }
061
062  private Bundle getTargetBundleOrCurrent(Bundle currentBundle, URL locationUrl) {
063    try {
064      Bundle targetBundle = currentBundle.getBundleContext().getBundle(getBundleId(locationUrl.getHost()));
065      return targetBundle != null ? targetBundle : currentBundle;
066    } catch (Exception e) {
067      return currentBundle;
068    }
069  }
070
071  private long getBundleId(String host) {
072    final Matcher matcher = bundleIdPattern.matcher(host);
073    if (matcher.find()) {
074      return Double.valueOf(matcher.group()).longValue();
075    }
076    throw new IllegalArgumentException("There's no bundleId in passed URL");
077  }
078
079  private String getPathWithoutLeadingSlash(URL entry) {
080    final String path = entry.getPath();
081
082    return path.startsWith("/") ? path.substring(1) : path;
083  }
084}