/**
* 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 org.arquillian.container.osgi.remote.processor;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.arquillian.container.osgi.remote.activator.ArquillianBundleActivator;
import org.arquillian.container.osgi.remote.processor.service.BundleActivatorsManager;
import org.arquillian.container.osgi.remote.processor.service.ImportPackageManager;
import org.arquillian.container.osgi.remote.processor.service.ManifestManager;
import org.jboss.arquillian.container.test.spi.RemoteLoadableExtension;
import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.spi.ServiceLoader;
import org.jboss.arquillian.protocol.jmx.JMXTestRunner;
import org.jboss.arquillian.test.spi.TestClass;
import org.jboss.osgi.metadata.OSGiManifestBuilder;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.container.ClassContainer;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Cristina González
*/
public class OSGiAllInProcessor implements ApplicationArchiveProcessor {
@Override
public void process(Archive<?> archive, TestClass testClass) {
try {
JavaArchive javaArchive = (JavaArchive)archive;
validateBundleArchive(javaArchive);
addTestClass(javaArchive, testClass);
addOSGiImports(javaArchive);
addArquillianDependencies(javaArchive);
List<Archive<?>> auxiliaryArchives = loadAuxiliaryArchives();
handleAuxiliaryArchives(javaArchive, auxiliaryArchives);
cleanRepeatedImports(javaArchive, auxiliaryArchives);
ManifestManager manifestManager = _manifestManagerInstance.get();
Manifest manifest = manifestManager.getManifest(javaArchive);
Attributes mainAttributes = manifest.getMainAttributes();
Attributes.Name bundleActivatorName = new Attributes.Name(
"Bundle-Activator");
String bundleActivator = mainAttributes.getValue(
bundleActivatorName);
mainAttributes.put(
bundleActivatorName,
ArquillianBundleActivator.class.getCanonicalName());
manifestManager.replaceManifest(javaArchive, manifest);
javaArchive.addClass(ArquillianBundleActivator.class);
if (bundleActivator != null) {
addBundleActivator(javaArchive, bundleActivator);
}
}
catch (RuntimeException rte) {
throw rte;
}
catch (Exception ex) {
throw new IllegalArgumentException(
"Not a valid OSGi bundle: " + archive, ex);
}
}
private void addArquillianDependencies(JavaArchive javaArchive) {
javaArchive.addPackage(JMXTestRunner.class.getPackage());
}
private void addBundleActivator(
JavaArchive javaArchive, String bundleActivatorValue)
throws IOException {
BundleActivatorsManager bundleActivatorsManager =
_bundleActivatorsManagerInstance.get();
List<String> bundleActivators =
bundleActivatorsManager.getBundleActivators(
javaArchive, _ACTIVATORS_FILE);
bundleActivators.add(bundleActivatorValue);
bundleActivatorsManager.replaceBundleActivatorsFile(
javaArchive, _ACTIVATORS_FILE, bundleActivators);
}
private void addOSGiImports(JavaArchive javaArchive) throws IOException {
String[] extensionsImports =
new String[] {"org.osgi.framework", "javax.management",
"javax.management.*", "javax.naming", "javax.naming.*",
"org.osgi.service.packageadmin", "org.osgi.service.startlevel",
"org.osgi.util.tracker"
};
ManifestManager manifestManager = _manifestManagerInstance.get();
Manifest manifest = manifestManager.putAttributeValue(
manifestManager.getManifest(javaArchive), "Import-Package",
extensionsImports);
manifestManager.replaceManifest(javaArchive, manifest);
}
private void addTestClass(JavaArchive javaArchive, TestClass testClass)
throws IOException {
Class<ClassContainer> classContainerClass = ClassContainer.class;
if (!classContainerClass.isAssignableFrom(javaArchive.getClass())) {
throw new IllegalArgumentException(
"ClassContainer expected: " + javaArchive);
}
// Get the test class and its super classes
Class<?> javaClass = testClass.getJavaClass();
Set<Class<?>> classes = new HashSet<>();
classes.add(javaClass);
Class<?> superclass = javaClass.getSuperclass();
while (superclass != Object.class) {
classes.add(superclass);
superclass = superclass.getSuperclass();
}
// Check if the application javaArchive already contains
// the test classes
String javaArchiveName = javaArchive.getName();
if (!javaArchiveName.endsWith(".war")) {
for (Class<?> clazz : classes) {
boolean testClassFound = false;
String className = clazz.getName();
String path = className.replace('.', '/') + ".class";
Map<ArchivePath, Node> javaArchiveContentMap =
javaArchive.getContent();
for (ArchivePath auxpath : javaArchiveContentMap.keySet()) {
if (auxpath.toString().endsWith(path)) {
testClassFound = true;
break;
}
}
if (!testClassFound) {
((ClassContainer<?>)javaArchive).addClass(clazz);
}
}
}
ManifestManager manifestManager = _manifestManagerInstance.get();
Manifest manifest = manifestManager.putAttributeValue(
manifestManager.getManifest(javaArchive), "Export-Package",
javaClass.getPackage().getName());
manifestManager.replaceManifest(javaArchive, manifest);
}
private void cleanRepeatedImports(
JavaArchive javaArchive, Collection<Archive<?>> auxiliaryArchives)
throws IOException {
ImportPackageManager importPackageManager =
_importPackageManagerInstance.get();
ManifestManager manifestManager = _manifestManagerInstance.get();
Manifest manifest = manifestManager.getManifest(javaArchive);
manifest = importPackageManager.cleanRepeatedImports(
manifest, auxiliaryArchives);
manifestManager.replaceManifest(javaArchive, manifest);
}
private void handleAuxiliaryArchives(
JavaArchive javaArchive, Collection<Archive<?>> auxiliaryArchives)
throws IOException {
for (Archive auxiliaryArchive : auxiliaryArchives) {
Map<ArchivePath, Node> remoteLoadableExtensionMap =
auxiliaryArchive.getContent(
Filters.include(_REMOTE_LOADABLE_EXTENSION_FILE));
Collection<Node> remoteLoadableExtensions =
remoteLoadableExtensionMap.values();
if (remoteLoadableExtensions.size() > 1) {
throw new RuntimeException(
"The archive " + auxiliaryArchive.getName() +
" contains more than one RemoteLoadableExtension file");
}
if (remoteLoadableExtensions.size() == 1) {
Iterator<Node> remoteLoadableExtensionsIterator =
remoteLoadableExtensions.iterator();
Node remoteLoadableExtensionsNext =
remoteLoadableExtensionsIterator.next();
javaArchive.add(
remoteLoadableExtensionsNext.getAsset(),
_REMOTE_LOADABLE_EXTENSION_FILE);
}
ZipExporter auxiliaryArchiveZipExporter = auxiliaryArchive.as(
ZipExporter.class);
InputStream auxiliaryArchiveInputStream =
auxiliaryArchiveZipExporter.exportAsInputStream();
ByteArrayAsset byteArrayAsset = new ByteArrayAsset(
auxiliaryArchiveInputStream);
String path = "extension/" + auxiliaryArchive.getName();
javaArchive.addAsResource(byteArrayAsset, path);
ManifestManager manifestManager = _manifestManagerInstance.get();
Manifest manifest = manifestManager.putAttributeValue(
manifestManager.getManifest(javaArchive), "Bundle-ClassPath",
".", path);
manifestManager.replaceManifest(javaArchive, manifest);
try {
validateBundleArchive(auxiliaryArchive);
Manifest auxiliaryArchiveManifest = manifestManager.getManifest(
(JavaArchive)auxiliaryArchive);
Attributes mainAttributes =
auxiliaryArchiveManifest.getMainAttributes();
String value = mainAttributes.getValue("Import-package");
if (value != null) {
String[] importValues = value.split(",");
manifest = manifestManager.putAttributeValue(
manifest, "Import-Package", importValues);
manifestManager.replaceManifest(javaArchive, manifest);
}
String bundleActivatorValue = mainAttributes.getValue(
"Bundle-Activator");
if ((bundleActivatorValue != null) &&
!bundleActivatorValue.isEmpty()) {
addBundleActivator(javaArchive, bundleActivatorValue);
}
}
catch (BundleException e) {
if (_logger.isInfoEnabled()) {
_logger.info(
"Not processing manifest from " + auxiliaryArchive +
": " + e.getMessage());
}
}
}
}
private List<Archive<?>> loadAuxiliaryArchives() {
List<Archive<?>> archives = new ArrayList<>();
// load based on the Containers ClassLoader
ServiceLoader serviceLoader = _serviceLoaderInstance.get();
Collection<AuxiliaryArchiveAppender> archiveAppenders =
serviceLoader.all(AuxiliaryArchiveAppender.class);
for (AuxiliaryArchiveAppender archiveAppender : archiveAppenders) {
Archive<?> auxiliaryArchive =
archiveAppender.createAuxiliaryArchive();
if (auxiliaryArchive != null) {
archives.add(auxiliaryArchive);
}
}
return archives;
}
private void validateBundleArchive(Archive<?> archive)
throws BundleException, IOException {
Manifest manifest = null;
Node node = archive.get(JarFile.MANIFEST_NAME);
if (node != null) {
manifest = new Manifest(node.getAsset().openStream());
}
if (manifest != null) {
OSGiManifestBuilder.validateBundleManifest(manifest);
}
else {
throw new BundleException("can't obtain Manifest");
}
}
private static final String _ACTIVATORS_FILE =
"/META-INF/services/" + BundleActivator.class.getCanonicalName();
private static final String _REMOTE_LOADABLE_EXTENSION_FILE =
"/META-INF/services/" +
RemoteLoadableExtension.class.getCanonicalName();
private static final Logger _logger = LoggerFactory.getLogger(
ApplicationArchiveProcessor.class);
@Inject
private Instance<BundleActivatorsManager> _bundleActivatorsManagerInstance;
@Inject
private Instance<ImportPackageManager> _importPackageManagerInstance;
@Inject
private Instance<ManifestManager> _manifestManagerInstance;
@Inject
private Instance<ServiceLoader> _serviceLoaderInstance;
}