/**
* 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.target.platform.indexer.internal;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.resource.CapabilityBuilder;
import com.liferay.portal.target.platform.indexer.Indexer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.osgi.framework.Bundle;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Capability;
import org.osgi.service.indexer.ResourceIndexer;
import org.osgi.service.indexer.impl.RepoIndex;
import org.osgi.service.repository.ContentNamespace;
/**
* @author Raymond Augé
*/
public class TargetPlatformIndexer implements Indexer {
public TargetPlatformIndexer(
Bundle systemBundle, List<File> additionalJarFiles,
String... dirNames) {
_systemBundle = systemBundle;
_additionalJarFiles = additionalJarFiles;
_dirNames = dirNames;
_config.put("compressed", "false");
_config.put(
"license.url", "https://www.liferay.com/downloads/ce-license");
_config.put("pretty", "true");
_config.put("repository.name", "Liferay Target Platform");
_config.put("stylesheet", "http://www.osgi.org/www/obr2html.xsl");
}
@Override
public void index(OutputStream outputStream) throws Exception {
Path tempPath = Files.createTempDirectory(null);
File tempDir = tempPath.toFile();
_config.put("root.url", tempDir.getPath());
Set<File> jarFiles = new LinkedHashSet<>();
try {
Object[] objects = _processSystemBundle(tempDir, jarFiles);
String sha256sum = (String)objects[0];
int size = (int)objects[1];
for (String dirName : _dirNames) {
Path path = Paths.get(dirName);
if (Files.notExists(path)) {
continue;
}
try (DirectoryStream<Path> directoryStream =
Files.newDirectoryStream(path, "*.jar")) {
Iterator<Path> iterator = directoryStream.iterator();
while (iterator.hasNext()) {
_addBundle(tempPath, iterator.next(), jarFiles);
}
}
}
for (File additionalJarFile : _additionalJarFiles) {
Path tempJarPath = tempPath.resolve(
additionalJarFile.getName());
Files.copy(
additionalJarFile.toPath(), tempJarPath,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING);
jarFiles.add(tempJarPath.toFile());
}
ResourceIndexer resourceIndexer = new RepoIndex();
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
resourceIndexer.index(jarFiles, byteArrayOutputStream, _config);
outputStream.write(
_fixSystemBundleOSGiContent(
byteArrayOutputStream.toString("UTF-8"), sha256sum, size));
}
finally {
PathUtil.deltree(tempPath);
}
}
private static String _bytesToHexString(byte[] bytes) {
char[] chars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
chars[i * 2] = _HEX_DIGITS[(bytes[i] & 0xFF) >> 4];
chars[i * 2 + 1] = _HEX_DIGITS[bytes[i] & 0x0F];
}
return new String(chars);
}
private void _addBundle(Path tempPath, Path jarPath, Set<File> jarFiles)
throws IOException {
Path tempJarPath = tempPath.resolve(jarPath.getFileName());
Files.copy(
jarPath, tempJarPath, StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING);
jarFiles.add(tempJarPath.toFile());
}
private byte[] _fixSystemBundleOSGiContent(
String content, String sha256sum, long size)
throws UnsupportedEncodingException {
String url =
_systemBundle.getSymbolicName() + "-" + _systemBundle.getVersion() +
".jar";
int index = content.indexOf(url);
if (index == -1) {
throw new IllegalStateException(
"Indexed content:\n" + content +
"\nMissing system bundle URL: " + url);
}
int start = content.lastIndexOf(_ATTRIBUTE_PREFIX_OSGI_CONTENT, index);
if (start == -1) {
throw new IllegalStateException(
"Indexed content:\n" + content.substring(0, index) +
"\nMissing OSGI content attribute: " +
_ATTRIBUTE_PREFIX_OSGI_CONTENT);
}
start += _ATTRIBUTE_PREFIX_OSGI_CONTENT.length();
int end = content.lastIndexOf("\"/>", index);
String prefix = content.substring(0, start);
String postfix = content.substring(end);
String newContent = prefix.concat(sha256sum).concat(postfix);
index = newContent.indexOf(url);
index += url.length() + 3;
start = newContent.indexOf(_ATTRIBUTE_PREFIX_SIZE, index);
if (start == -1) {
throw new IllegalStateException(
"Indexed content:\n" + content + "\nMissing size attribute: " +
_ATTRIBUTE_PREFIX_SIZE);
}
start += _ATTRIBUTE_PREFIX_SIZE.length();
end = newContent.indexOf("\"/>", index);
prefix = newContent.substring(0, start);
postfix = newContent.substring(end);
newContent = prefix.concat(String.valueOf(size)).concat(postfix);
newContent = newContent.replace("\r\n", "\n");
return newContent.getBytes("UTF-8");
}
private void _processBundle(Bundle bundle) throws Exception {
BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
for (Capability capability : bundleRevision.getCapabilities(null)) {
String namespace = capability.getNamespace();
CapabilityBuilder capabilityBuilder = new CapabilityBuilder(
namespace);
capabilityBuilder.addAttributes(capability.getAttributes());
capabilityBuilder.addDirectives(capability.getDirectives());
Attrs attrs = capabilityBuilder.toAttrs();
if (capabilityBuilder.isPackage()) {
attrs.remove(Constants.BUNDLE_SYMBOLIC_NAME_ATTRIBUTE);
attrs.remove(Constants.BUNDLE_VERSION_ATTRIBUTE);
String packageName = attrs.remove(
PackageNamespace.PACKAGE_NAMESPACE);
if (packageName != null) {
_packagesParamters.put(packageName, attrs);
}
}
else if (!_ignoredNamespaces.contains(namespace)) {
Parameters parameters = new Parameters();
if (namespace.equals(NativeNamespace.NATIVE_NAMESPACE)) {
Set<String> keys = new LinkedHashSet<>(attrs.keySet());
for (String key : keys) {
if (!key.startsWith(NativeNamespace.NATIVE_NAMESPACE)) {
attrs.remove(key);
}
}
}
parameters.put(namespace, attrs);
_parametersList.add(parameters);
}
}
}
private Object[] _processSystemBundle(File tempDir, Set<File> jarFiles)
throws Exception {
try (Jar jar = new Jar("system.bundle")) {
_processBundle(_systemBundle);
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue(
Constants.BUNDLE_SYMBOLICNAME, _systemBundle.getSymbolicName());
attributes.putValue(
Constants.BUNDLE_VERSION,
_systemBundle.getVersion().toString());
String exportPackage = _packagesParamters.toString();
exportPackage = exportPackage.replace("version:Version", "version");
attributes.putValue(Constants.EXPORT_PACKAGE, exportPackage);
StringBuilder sb = new StringBuilder();
for (Parameters parameter : _parametersList) {
String parameterString = parameter.toString();
if (parameterString.startsWith("osgi.ee;osgi.ee=")) {
if (parameterString.startsWith("osgi.ee;osgi.ee=JavaSE")) {
sb.append(_PARAMETER_STRING_JDK_VERSION);
sb.append(",");
}
continue;
}
else if (parameterString.startsWith("eclipse.platform;")) {
parameterString = _PARAMETER_STRING_OS_VERSION;
}
sb.append(parameterString);
sb.append(",");
}
sb.setLength(sb.length() - 1);
String capabilities = sb.toString();
attributes.putValue(Constants.PROVIDE_CAPABILITY, capabilities);
jar.setManifest(manifest);
File jarFile = new File(
tempDir,
_systemBundle.getSymbolicName() + "-" +
_systemBundle.getVersion() + ".jar");
jar.write(jarFile);
jarFiles.add(jarFile);
try (ZipFile zipFile = new ZipFile(jarFile)) {
ZipEntry zipEntry = zipFile.getEntry("META-INF/MANIFEST.MF");
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
try (InputStream inputStream = zipFile.getInputStream(
zipEntry)) {
byte[] buffer = new byte[4096];
int size = -1;
while ((size = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, size);
}
}
MessageDigest messageDigest = MessageDigest.getInstance(
"SHA-256");
messageDigest.update(byteArrayOutputStream.toByteArray());
return new Object[] {
_bytesToHexString(messageDigest.digest()),
byteArrayOutputStream.size()
};
}
}
}
private static final String _ATTRIBUTE_PREFIX_OSGI_CONTENT =
"<attribute name=\"osgi.content\" value=\"";
private static final String _ATTRIBUTE_PREFIX_SIZE =
"<attribute name=\"size\" type=\"Long\" value=\"";
private static final char[] _HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'
};
private static final String _PARAMETER_STRING_JDK_VERSION =
"osgi.ee;osgi.ee=JavaSE;version:List<Version>=\"1.0.0,1.1.0,1.2.0," +
"1.3.0,1.4.0,1.5.0,1.6.0,1.7.0,1.8.0\"";
private static final String _PARAMETER_STRING_OS_VERSION =
"eclipse.platform;osgi.os=linux;osgi.arch=x86_64;osgi.ws=gtk;osgi.nl=" +
"en_US";
private static final Set<String> _ignoredNamespaces = new HashSet<>();
static {
_ignoredNamespaces.add(BundleNamespace.BUNDLE_NAMESPACE);
_ignoredNamespaces.add(ContentNamespace.CONTENT_NAMESPACE);
_ignoredNamespaces.add(HostNamespace.HOST_NAMESPACE);
_ignoredNamespaces.add(IdentityNamespace.IDENTITY_NAMESPACE);
_ignoredNamespaces.add(NativeNamespace.NATIVE_NAMESPACE);
_ignoredNamespaces.add(PackageNamespace.PACKAGE_NAMESPACE);
}
private final List<File> _additionalJarFiles;
private final Map<String, String> _config = new HashMap<>();
private final String[] _dirNames;
private final Parameters _packagesParamters = new Parameters();
private final List<Parameters> _parametersList = new ArrayList<>();
private final Bundle _systemBundle;
}