diff --git a/modules/apps/static/osgi/osgi-util/src/main/java/com/liferay/osgi/util/BundleUtil.java b/modules/apps/static/osgi/osgi-util/src/main/java/com/liferay/osgi/util/BundleUtil.java
index 02dfe9b4de503eaf39eef9192db1c97e4d159933..f38d94d424ccea49950a610bfe7d9c97646907cd 100644
--- a/modules/apps/static/osgi/osgi-util/src/main/java/com/liferay/osgi/util/BundleUtil.java
+++ b/modules/apps/static/osgi/osgi-util/src/main/java/com/liferay/osgi/util/BundleUtil.java
@@ -14,16 +14,40 @@
 
 package com.liferay.osgi.util;
 
+import com.liferay.osgi.util.bundle.BundleStartLevelUtil;
+import com.liferay.petra.reflect.ReflectionUtil;
+import com.liferay.petra.string.CharPool;
+import com.liferay.petra.string.StringPool;
+import com.liferay.portal.kernel.concurrent.DefaultNoticeableFuture;
+import com.liferay.portal.kernel.util.ArrayUtil;
+import com.liferay.portal.kernel.util.StringUtil;
+import com.liferay.portal.kernel.util.Validator;
+import com.liferay.portal.lpkg.deployer.LPKGDeployer;
+import com.liferay.portal.osgi.web.wab.generator.WabGenerator;
+
+import java.io.File;
+
+import java.net.URI;
 import java.net.URL;
 
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.util.tracker.ServiceTracker;
 
 /**
- * @author Carlos Sierra Andrés
+ * @author Matthew Tambara
  */
 public class BundleUtil {
 
@@ -63,4 +87,199 @@ public class BundleUtil {
 		return list.get(list.size() - 1);
 	}
 
+	public static void installBundle(
+			BundleContext bundleContext, LPKGDeployer lpkgDeployer,
+			String location, int startLevel)
+		throws Exception {
+
+		Bundle bundle = null;
+
+		Map<String, String[]> parameters = _getParameterMap(location);
+
+		String[] lpkgPath = parameters.get("lpkgPath");
+		String[] protocol = parameters.get("protocol");
+		String[] webContextPath = parameters.get("Web-ContextPath");
+
+		if (parameters.isEmpty() && location.endsWith(".lpkg")) {
+			URI uri = new URI(location);
+
+			uri = uri.normalize();
+
+			bundle = bundleContext.installBundle(
+				location, lpkgDeployer.toBundle(new File(uri.getPath())));
+		}
+		else if (ArrayUtil.isNotEmpty(lpkgPath)) {
+			bundle = bundleContext.getBundle(lpkgPath[0]);
+
+			refreshBundles(
+				bundleContext, Collections.<Bundle>singletonList(bundle));
+
+			return;
+		}
+		else if (ArrayUtil.isNotEmpty(protocol) && protocol[0].equals("lpkg") &&
+				 ArrayUtil.isNotEmpty(webContextPath)) {
+
+			String contextName = webContextPath[0].substring(1);
+
+			for (Bundle installedBundle : bundleContext.getBundles()) {
+				Dictionary<String, String> headers = installedBundle.getHeaders(
+					StringPool.BLANK);
+
+				if (contextName.equals(
+						headers.get("Liferay-WAB-Context-Name"))) {
+
+					refreshBundles(
+						bundleContext,
+						Collections.<Bundle>singletonList(installedBundle));
+				}
+			}
+
+			return;
+		}
+		else if (location.startsWith("webbundle:")) {
+			WebBundleInstaller webBundleInstaller = new WebBundleInstaller(
+				bundleContext, location, startLevel);
+
+			webBundleInstaller.open();
+
+			return;
+		}
+		else {
+			bundle = bundleContext.installBundle(location);
+		}
+
+		BundleStartLevelUtil.setStartLevelAndStart(
+			bundle, startLevel, bundleContext);
+	}
+
+	public static void refreshBundles(
+		BundleContext bundleContext, List<Bundle> refreshBundles) {
+
+		Bundle systemBundle = bundleContext.getBundle(0);
+
+		FrameworkWiring frameworkWiring = systemBundle.adapt(
+			FrameworkWiring.class);
+
+		final DefaultNoticeableFuture<FrameworkEvent> defaultNoticeableFuture =
+			new DefaultNoticeableFuture<>();
+
+		frameworkWiring.refreshBundles(
+			refreshBundles,
+			new FrameworkListener() {
+
+				@Override
+				public void frameworkEvent(FrameworkEvent frameworkEvent) {
+					defaultNoticeableFuture.set(frameworkEvent);
+				}
+
+			});
+
+		try {
+			FrameworkEvent frameworkEvent = defaultNoticeableFuture.get();
+
+			if (frameworkEvent.getType() != FrameworkEvent.PACKAGES_REFRESHED) {
+				throw frameworkEvent.getThrowable();
+			}
+		}
+		catch (Throwable t) {
+			ReflectionUtil.throwException(t);
+		}
+	}
+
+	private static Map<String, String[]> _getParameterMap(String location) {
+		int index = location.indexOf(CharPool.QUESTION);
+
+		if (index == -1) {
+			return Collections.emptyMap();
+		}
+
+		String queryString = location.substring(index + 1);
+
+		if (Validator.isNull(queryString)) {
+			return Collections.emptyMap();
+		}
+
+		String[] parameters = StringUtil.split(queryString, CharPool.AMPERSAND);
+
+		Map<String, String[]> parameterMap = new HashMap<>();
+
+		for (String parameter : parameters) {
+			if (parameter.length() > 0) {
+				String[] kvp = StringUtil.split(parameter, CharPool.EQUAL);
+
+				if (kvp.length == 0) {
+					continue;
+				}
+
+				String key = kvp[0];
+
+				String value = StringPool.BLANK;
+
+				if (kvp.length > 1) {
+					value = kvp[1];
+				}
+
+				String[] values = parameterMap.get(key);
+
+				if (values == null) {
+					parameterMap.put(key, new String[] {value});
+				}
+				else {
+					parameterMap.put(key, ArrayUtil.append(values, value));
+				}
+			}
+		}
+
+		return parameterMap;
+	}
+
+	private static class WebBundleInstaller
+		extends ServiceTracker<WabGenerator, Void> {
+
+		public WebBundleInstaller(
+				BundleContext bundleContext, String location, int startLevel)
+			throws InvalidSyntaxException {
+
+			super(bundleContext, WabGenerator.class, null);
+
+			_location = location;
+			_startLevel = startLevel;
+		}
+
+		@Override
+		public Void addingService(
+			ServiceReference<WabGenerator> serviceReference) {
+
+			// Service must be explicitly gotten from the bundle context to
+			// ensure DS component's lazy activation is completed
+
+			WabGenerator wabGenerator = context.getService(serviceReference);
+
+			if (wabGenerator == null) {
+				throw new IllegalStateException("Missing WAB generator");
+			}
+
+			try {
+				Bundle bundle = context.installBundle(_location);
+
+				BundleStartLevelUtil.setStartLevelAndStart(
+					bundle, _startLevel, context);
+			}
+			catch (Exception exception) {
+				ReflectionUtil.throwException(exception);
+			}
+			finally {
+				context.ungetService(serviceReference);
+			}
+
+			close();
+
+			return null;
+		}
+
+		private final String _location;
+		private final int _startLevel;
+
+	}
+
 }
\ No newline at end of file