diff --git a/liquibase-core/pom.xml b/liquibase-core/pom.xml
index 70f2fbc5f60..0fdcaadc938 100644
--- a/liquibase-core/pom.xml
+++ b/liquibase-core/pom.xml
@@ -57,13 +57,13 @@
1.3
test
-
+
org.osgi
org.osgi.core
provided
- 4.3.1
-
+ 5.0.0
+
org.springframework
@@ -155,10 +155,11 @@
org.apache.felix
maven-bundle-plugin
- 3.3.0
+ 5.1.2
org.liquibase.core
+ liquibase.osgi.Activator
javax.activation*;resolution:=optional,
javax.servlet.*;version="[2.6,4)";resolution:=optional,
@@ -167,6 +168,66 @@
org.yaml.snakeyaml.*,
*;resolution:=optional
+
+ osgi.serviceloader; osgi.serviceloader=liquibase.serializer.ChangeLogSerializer,
+ osgi.serviceloader; osgi.serviceloader=liquibase.parser.NamespaceDetails,
+ osgi.serviceloader; osgi.serviceloader=liquibase.database.Database,
+ osgi.serviceloader; osgi.serviceloader=liquibase.change.Change,
+ osgi.serviceloader; osgi.serviceloader=liquibase.database.DatabaseConnection,
+ osgi.serviceloader; osgi.serviceloader=liquibase.precondition.Precondition,
+ osgi.serviceloader; osgi.serviceloader=liquibase.serializer.SnapshotSerializer,
+ osgi.serviceloader; osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations,
+ osgi.serviceloader; osgi.serviceloader=liquibase.diff.DiffGenerator,
+ osgi.serviceloader; osgi.serviceloader=liquibase.lockservice.LockService,
+ osgi.serviceloader; osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService,
+ osgi.serviceloader; osgi.serviceloader=liquibase.datatype.LiquibaseDataType,
+ osgi.serviceloader; osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider,
+ osgi.serviceloader; osgi.serviceloader=liquibase.logging.LogService,
+ osgi.serviceloader; osgi.serviceloader=liquibase.snapshot.SnapshotGenerator,
+ osgi.serviceloader; osgi.serviceloader=liquibase.parser.ChangeLogParser,
+ osgi.serviceloader; osgi.serviceloader=liquibase.servicelocator.ServiceLocator,
+ osgi.serviceloader; osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator,
+ osgi.serviceloader; osgi.serviceloader=liquibase.command.LiquibaseCommand,
+ osgi.serviceloader; osgi.serviceloader=liquibase.license.LicenseService,
+ osgi.serviceloader; osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator,
+ osgi.serviceloader; osgi.serviceloader=liquibase.executor.Executor,
+ osgi.serviceloader; osgi.serviceloader=liquibase.structure.DatabaseObject,
+ osgi.serviceloader; osgi.serviceloader=liquibase.parser.SnapshotParser,
+ osgi.serviceloader; osgi.serviceloader=liquibase.hub.HubService,
+ osgi.serviceloader; osgi.serviceloader=liquibase.command.CommandStep,
+ osgi.serviceloader; osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator
+
+
+ osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)",
+ osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)",
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.ChangeLogSerializer)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.NamespaceDetails)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.Database)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.change.Change)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.DatabaseConnection)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.precondition.Precondition)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.SnapshotSerializer)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.DiffGenerator)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.lockservice.LockService)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.datatype.LiquibaseDataType)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.logging.LogService)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.snapshot.SnapshotGenerator)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.ChangeLogParser)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.servicelocator.ServiceLocator)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.LiquibaseCommand)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.license.LicenseService)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.executor.Executor)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.structure.DatabaseObject)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.SnapshotParser)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.hub.HubService)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.CommandStep)"; cardinality:=multiple,
+ osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator)"; cardinality:=multiple
+
diff --git a/liquibase-core/src/main/java/liquibase/Scope.java b/liquibase-core/src/main/java/liquibase/Scope.java
index 25a5aa3adcb..0910d57134c 100644
--- a/liquibase-core/src/main/java/liquibase/Scope.java
+++ b/liquibase-core/src/main/java/liquibase/Scope.java
@@ -11,6 +11,7 @@
import liquibase.logging.Logger;
import liquibase.logging.core.JavaLogService;
import liquibase.logging.core.LogServiceFactory;
+import liquibase.osgi.Activator;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.servicelocator.ServiceLocator;
@@ -55,6 +56,7 @@ public enum Attr {
fileEncoding,
databaseChangeLog,
changeSet,
+ osgiPlatform
}
private static ScopeManager scopeManager;
@@ -95,6 +97,7 @@ public static Scope getCurrentScope() {
}
rootScope.values.put(Attr.serviceLocator.name(), serviceLocator);
+ rootScope.values.put(Attr.osgiPlatform.name(), Activator.OSGIContainerChecker.isOsgiPlatform());
}
return scopeManager.getCurrentScope();
}
diff --git a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java
index 1f4e4f94d9a..f86c0b68025 100644
--- a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java
+++ b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java
@@ -14,6 +14,7 @@
import liquibase.util.ObjectUtil;
import java.util.*;
+import liquibase.util.OsgiUtil;
/**
* Adapts CustomChange implementations to the standard change system used by Liquibase.
@@ -69,16 +70,21 @@ public CustomChangeWrapper setClass(String className) throws CustomChangeExcepti
return this;
}
this.className = className;
- try {
+ try {
+ Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class);
+ if (Boolean.TRUE.equals(osgiPlatform)) {
+ customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance();
+ } else {
+ try {
+ customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance();
+ } catch (ClassCastException e) { //fails in Ant in particular
try {
- customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance();
- } catch (ClassCastException e) { //fails in Ant in particular
- try {
- customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance();
- } catch (ClassNotFoundException e1) {
- customChange = (CustomChange) Class.forName(className).getConstructor().newInstance();
- }
+ customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance();
+ } catch (ClassNotFoundException e1) {
+ customChange = (CustomChange) Class.forName(className).getConstructor().newInstance();
}
+ }
+ }
} catch (Exception e) {
throw new CustomChangeException(e);
}
@@ -316,7 +322,12 @@ public void customLoadLogic(ParsedNode parsedNode, ResourceAccessor resourceAcce
CustomChange customChange = null;
try {
- customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance();
+ Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class);
+ if (Boolean.TRUE.equals(osgiPlatform)) {
+ customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance();
+ } else {
+ customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance();
+ }
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
diff --git a/liquibase-core/src/main/java/liquibase/osgi/Activator.java b/liquibase-core/src/main/java/liquibase/osgi/Activator.java
new file mode 100644
index 00000000000..673a53cebe1
--- /dev/null
+++ b/liquibase-core/src/main/java/liquibase/osgi/Activator.java
@@ -0,0 +1,108 @@
+package liquibase.osgi;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import liquibase.osgi.Activator.LiquibaseBundle;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+
+public class Activator implements BundleActivator, BundleTrackerCustomizer {
+
+ private static final String LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES = "Liquibase-Custom-Change-Packages";
+ private BundleTracker bundleTracker;
+ private static final List liquibaseBundles = new CopyOnWriteArrayList<>();
+
+ @Override
+ public void start(final BundleContext bc) throws Exception {
+ OSGIContainerChecker.osgiPlatform();
+ bundleTracker = new BundleTracker<>(bc, Bundle.ACTIVE, this);
+ bundleTracker.open();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ bundleTracker.close();
+ liquibaseBundles.clear();
+ }
+
+ public static List getLiquibaseBundles() {
+ return Collections.unmodifiableList(liquibaseBundles);
+ }
+
+ @Override
+ public LiquibaseBundle addingBundle(Bundle bundle, BundleEvent event) {
+ if (bundle.getBundleId() == 0) {
+ return null;
+ }
+ String customWrapperPackages = (String) bundle.getHeaders().get(LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES);
+ if (customWrapperPackages != null) {
+ LiquibaseBundle lb = new LiquibaseBundle(bundle, customWrapperPackages);
+ liquibaseBundles.add(lb);
+ return lb;
+ }
+ return null;
+ }
+
+ @Override
+ public void modifiedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) {
+ // nothing to do
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) {
+ if (liquibaseBundle != null) {
+ liquibaseBundles.remove(liquibaseBundle);
+ }
+ }
+
+ public static class LiquibaseBundle {
+
+ private final Bundle bundle;
+ private final List allowedPackages;
+
+ public LiquibaseBundle(Bundle bundle, String allowedPackages) {
+ if (bundle == null) {
+ throw new IllegalArgumentException("bundle cannot be empty");
+ }
+ if (allowedPackages == null || allowedPackages.isEmpty()) {
+ throw new IllegalArgumentException("packages cannot be empty");
+ }
+ this.bundle = bundle;
+ this.allowedPackages = Collections.unmodifiableList(Arrays.asList(allowedPackages.split(",")));
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ public boolean allowedAllPackages() {
+ return allowedPackages.size() == 1
+ && "*".equals(allowedPackages.get(0));
+ }
+
+ public List getAllowedPackages() {
+ return allowedPackages;
+ }
+
+ }
+
+ public static class OSGIContainerChecker {
+
+ private static volatile boolean osgiPlatform = false;
+
+ public static boolean isOsgiPlatform() {
+ return osgiPlatform;
+ }
+
+ static void osgiPlatform() {
+ osgiPlatform = true;
+ }
+ }
+
+}
diff --git a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java
index 8b56d2ca485..de89b1868c8 100644
--- a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java
+++ b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java
@@ -1,6 +1,8 @@
package liquibase.util;
import liquibase.Scope;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
import java.io.IOException;
import java.io.InputStream;
@@ -54,19 +56,37 @@ public static String getBuildNumber() {
// the jar file.
private static String getBuildInfo(String propertyId) {
if (liquibaseBuildProperties == null) {
- try {
- liquibaseBuildProperties = new Properties();
- final Enumeration propertiesUrls = Scope.getCurrentScope().getClassLoader().getResources("liquibase.build.properties");
- while (propertiesUrls.hasMoreElements()) {
- final URL url = propertiesUrls.nextElement();
- try (InputStream buildProperties = url.openStream()) {
+ Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class);
+ if (Boolean.TRUE.equals(osgiPlatform)) {
+ Bundle bundle = FrameworkUtil.getBundle(LiquibaseUtil.class);
+ URL propURL = bundle.getEntry("liquibase.build.properties");
+ if (propURL == null) {
+ Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties");
+ } else {
+ try (InputStream buildProperties = propURL.openStream()) {
+ liquibaseBuildProperties = new Properties();
if (buildProperties != null) {
liquibaseBuildProperties.load(buildProperties);
}
+ } catch (IOException e) {
+ Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e);
}
}
- } catch (IOException e) {
- Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e);
+ } else {
+ try {
+ liquibaseBuildProperties = new Properties();
+ final Enumeration propertiesUrls = Scope.getCurrentScope().getClassLoader().getResources("liquibase.build.properties");
+ while (propertiesUrls.hasMoreElements()) {
+ final URL url = propertiesUrls.nextElement();
+ try (InputStream buildProperties = url.openStream()) {
+ if (buildProperties != null) {
+ liquibaseBuildProperties.load(buildProperties);
+ }
+ }
+ }
+ } catch (IOException e) {
+ Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e);
+ }
}
}
diff --git a/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java
new file mode 100644
index 00000000000..71a39077f77
--- /dev/null
+++ b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java
@@ -0,0 +1,65 @@
+package liquibase.util;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import liquibase.osgi.Activator;
+import liquibase.osgi.Activator.LiquibaseBundle;
+
+public final class OsgiUtil {
+
+ private OsgiUtil() {
+ }
+
+ /**
+ * try to load a class under OSGI environment. It will try to load the class
+ * from all liquibase bundles registered via
+ * {@link Activator Activator}
+ *
+ * @param
+ * @param className name of class
+ * @return
+ * @throws ClassNotFoundException
+ */
+ public static Class loadClass(String className) throws ClassNotFoundException {
+ List liquibaseBundles = Activator.getLiquibaseBundles();
+ for (LiquibaseBundle lb : liquibaseBundles) {
+ try {
+ Class clazz = (Class) lb.getBundle().loadClass(className);
+ if (!isClassAllowed(lb, clazz)) {
+ throw new ClassNotFoundException("Class is not allowed to load, class:" + className + " bundles:"
+ + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName())
+ .collect(Collectors.joining(",")));
+ }
+ return clazz;
+ } catch (ClassNotFoundException ex) {
+ // nothing to do
+ }
+ }
+ throw new ClassNotFoundException("Cannot find class:" + className + " bundles:"
+ + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName())
+ .collect(Collectors.joining(",")));
+ }
+
+ /**
+ *
+ * @param clazz
+ * @return true is a class is allowed
+ * @throws java.lang.ClassNotFoundException
+ */
+ private static boolean isClassAllowed(LiquibaseBundle liquibaseBundle, Class clazz) {
+ if (liquibaseBundle.allowedAllPackages()) {
+ return true;
+ }
+ for (String allowedPackage : liquibaseBundle.getAllowedPackages()) {
+ Package pkg = clazz.getPackage();
+ if (pkg != null) {
+ String pkgName = pkg.getName();
+ if (allowedPackage.equals(pkgName) || allowedPackage.startsWith(pkgName + ".")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}