/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.osgi.web.wab.generator.internal; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle; import com.liferay.portal.kernel.util.HashMapDictionary; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.osgi.web.wab.generator.internal.artifact.ArtifactURLUtil; import com.liferay.portal.osgi.web.wab.generator.internal.artifact.WarArtifactUrlTransformer; import com.liferay.portal.osgi.web.wab.generator.internal.handler.WabURLStreamHandlerService; import com.liferay.portal.osgi.web.wab.generator.internal.processor.WabProcessor; import com.liferay.portal.util.PropsValues; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Dictionary; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.servlet.ServletContext; import org.apache.felix.fileinstall.ArtifactUrlTransformer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.url.URLConstants; import org.osgi.service.url.URLStreamHandlerService; import org.osgi.util.tracker.BundleTracker; /** * @author Miguel Pastor * @author Raymond Augé */ @Component(immediate = true) public class WabGenerator implements com.liferay.portal.osgi.web.wab.generator.WabGenerator { @Override public File generate( ClassLoader classLoader, File file, Map<String, String[]> parameters) throws IOException { WabProcessor wabProcessor = new WabProcessor( classLoader, file, parameters); return wabProcessor.getProcessedFile(); } @Activate public void start(BundleContext bundleContext) throws Exception { registerURLStreamHandlerService(bundleContext); registerArtifactUrlTransformer(bundleContext); final Set<String> requiredForStartupLocations = getRequiredForStartupLocations( Paths.get(PropsValues.LIFERAY_HOME, "osgi/war")); if (requiredForStartupLocations.isEmpty()) { return; } final CountDownLatch countDownLatch = new CountDownLatch(1); BundleTracker<Void> bundleTracker = new BundleTracker<Void>( bundleContext, Bundle.ACTIVE, null) { @Override public Void addingBundle(Bundle bundle, BundleEvent bundleEvent) { String location = StringUtil.toLowerCase(bundle.getLocation()); if (_log.isDebugEnabled()) { _log.debug("Activated bundle " + location); } if (requiredForStartupLocations.remove(location)) { if (_log.isDebugEnabled()) { _log.debug( "Bundle " + location + " is required for startup"); } if (requiredForStartupLocations.isEmpty()) { countDownLatch.countDown(); } } return null; } }; if (_log.isDebugEnabled()) { _log.debug( "Bundles required for startup: " + requiredForStartupLocations); } bundleTracker.open(); countDownLatch.await(); bundleTracker.close(); } @Deactivate public void stop(BundleContext bundleContext) throws Exception { _serviceRegistration.unregister(); _serviceRegistration = null; } protected Set<String> getRequiredForStartupLocations(Path path) throws IOException { Set<String> locations = new HashSet<>(); try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path.toRealPath(), "*.war")) { for (Path warPath : directoryStream) { URI uri = warPath.toUri(); try (ZipFile zipFile = new ZipFile(new File(uri)); InputStream inputStream = zipFile.getInputStream( new ZipEntry( "WEB-INF/liferay-plugin-package.properties"))) { if (inputStream == null) { continue; } Properties properties = new Properties(); properties.load(inputStream); if (!Boolean.valueOf( properties.getProperty("required-for-startup"))) { continue; } URL url = ArtifactURLUtil.transform(uri.toURL()); locations.add(StringUtil.toLowerCase(url.toString())); } } } return locations; } protected void registerArtifactUrlTransformer(BundleContext bundleContext) { _serviceRegistration = bundleContext.registerService( ArtifactUrlTransformer.class, new WarArtifactUrlTransformer(_portalIsReady), null); } protected void registerURLStreamHandlerService( BundleContext bundleContext) { Bundle bundle = bundleContext.getBundle(0); Class<?> clazz = bundle.getClass(); ClassLoader classLoader = clazz.getClassLoader(); Dictionary<String, Object> properties = new HashMapDictionary<>(); properties.put( URLConstants.URL_HANDLER_PROTOCOL, new String[] {"webbundle"}); bundleContext.registerService( URLStreamHandlerService.class.getName(), new WabURLStreamHandlerService(classLoader, this), properties); } /** * This reference is held to force a dependency on the portal's complete * startup. */ @Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED, unbind = "-") protected void setModuleServiceLifecycle( ModuleServiceLifecycle moduleServiceLifecycle) { } @Reference( cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, target = "(&(original.bean=true)(bean.id=javax.servlet.ServletContext))" ) protected void setServletContext(ServletContext servletContext) { _portalIsReady.set(true); } protected void unsetModuleServiceLifecycle( ModuleServiceLifecycle moduleServiceLifecycle) { } protected void unsetServletContext(ServletContext servletContext) { _portalIsReady.set(false); } private static final Log _log = LogFactoryUtil.getLog(WabGenerator.class); private final AtomicBoolean _portalIsReady = new AtomicBoolean(); private ServiceRegistration<ArtifactUrlTransformer> _serviceRegistration; }