/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.installer.factories.subsystems.base.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.tasks.RegisteredResource;
import org.apache.sling.installer.api.tasks.ResourceTransformer;
import org.apache.sling.installer.api.tasks.TransformationResult;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.service.subsystem.SubsystemConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SubsystemBaseTransformer implements ResourceTransformer {
private static final String POTENTIAL_BUNDLES = "Potential_Bundles/";
private static final String TYPE_SUBSYSTEM_BASE = "subsystem-base";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SlingSettingsService slingSettings;
public SubsystemBaseTransformer(SlingSettingsService settings) {
slingSettings = settings;
}
public TransformationResult[] transform(RegisteredResource resource) {
// TODO start level of the subsystem
if ( resource.getType().equals(InstallableResource.TYPE_FILE) ) {
if ( resource.getURL().endsWith("." + TYPE_SUBSYSTEM_BASE) ) {
logger.info("Found subsystem-base resource {}", resource);
try {
SubsystemData ssd = createSubsystemFile(resource);
TransformationResult tr = new TransformationResult();
Attributes mfAttributes = ssd.manifest.getMainAttributes();
tr.setId(mfAttributes.getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME));
tr.setVersion(new Version(mfAttributes.getValue(SubsystemConstants.SUBSYSTEM_VERSION)));
tr.setResourceType("esa");
tr.setInputStream(new DeleteOnCloseFileInputStream(ssd.file));
Map<String, Object> attr = new HashMap<String, Object>();
attr.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, mfAttributes.getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME));
attr.put(SubsystemConstants.SUBSYSTEM_VERSION, mfAttributes.getValue(SubsystemConstants.SUBSYSTEM_VERSION));
tr.setAttributes(attr);
return new TransformationResult[] {tr};
} catch (IOException ioe) {
logger.error("Problem processing subsystem-base file " + resource, ioe);
}
}
}
return null;
}
private SubsystemData createSubsystemFile(RegisteredResource resource) throws IOException {
SubsystemData data = new SubsystemData();
StringBuilder subsystemContent = new StringBuilder();
try (JarInputStream jis = new JarInputStream(resource.getInputStream())) {
Manifest runModesManifest = jis.getManifest();
Set<String> runModeResources = processRunModesManifest(runModesManifest);
File zf = File.createTempFile("sling-generated", ".esa");
zf.deleteOnExit();
data.file = zf;
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zf))) {
ZipEntry zie = null;
while((zie = jis.getNextEntry()) != null) {
String zieName = zie.getName();
if ("SUBSYSTEM-MANIFEST-BASE.MF".equals(zieName)) {
data.manifest = new Manifest(jis);
} else if (runModeResources.contains(zieName)) {
zieName = zieName.substring(POTENTIAL_BUNDLES.length());
handleSubsystemArtifact(zieName, jis, zos, subsystemContent);
}
}
data.manifest.getMainAttributes().putValue(SubsystemConstants.SUBSYSTEM_CONTENT, subsystemContent.toString());
ZipEntry zoe = new ZipEntry("OSGI-INF/SUBSYSTEM.MF");
try {
zos.putNextEntry(zoe);
data.manifest.write(zos);
} finally {
zos.closeEntry();
}
}
}
return data;
}
private Set<String> processRunModesManifest(Manifest runModesManifest) {
Set<String> res = new HashSet<>();
Attributes attrs = runModesManifest.getMainAttributes();
res.addAll(parseManifestHeader(attrs, "_all_"));
for (String rm : slingSettings.getRunModes()) {
res.addAll(parseManifestHeader(attrs, rm));
}
return res;
}
private List<String> parseManifestHeader(Attributes attrs, String key) {
List<String> res = new ArrayList<>();
String val = attrs.getValue(key);
if (val == null)
return Collections.emptyList();
for (String r : val.split("[|]")) {
if (r.length() > 0)
res.add(r);
}
return res;
}
private void handleSubsystemArtifact(String artifactName, ZipInputStream zis, ZipOutputStream zos,
StringBuilder subsystemContent) throws IOException {
int idx = artifactName.indexOf('/');
int idx2 = artifactName.lastIndexOf('/');
if (idx != idx2 || idx == 0)
throw new IOException("Invalid entry name, should have format .../<num>/artifact.jar: " + artifactName);
int startOrder = Integer.parseInt(artifactName.substring(0, idx));
idx++;
if (artifactName.length() > idx) {
File tempArtifact = File.createTempFile("sling-generated", ".tmp");
tempArtifact.deleteOnExit();
Path tempPath = tempArtifact.toPath();
Files.copy(zis, tempPath, StandardCopyOption.REPLACE_EXISTING);
try (JarFile jf = new JarFile(tempArtifact)) {
Attributes ma = jf.getManifest().getMainAttributes();
String bsn = ma.getValue(Constants.BUNDLE_SYMBOLICNAME);
String version = ma.getValue(Constants.BUNDLE_VERSION);
if (version == null)
version = "0";
String type = ma.getValue(Constants.FRAGMENT_HOST) != null ?
IdentityNamespace.TYPE_FRAGMENT : IdentityNamespace.TYPE_BUNDLE;
if (bsn != null) {
if (subsystemContent.length() > 0)
subsystemContent.append(',');
subsystemContent.append(bsn);
subsystemContent.append(';');
subsystemContent.append(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
subsystemContent.append('=');
subsystemContent.append(version);
subsystemContent.append(';');
subsystemContent.append(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE);
subsystemContent.append('=');
subsystemContent.append(type);
subsystemContent.append(';');
subsystemContent.append(SubsystemConstants.START_ORDER_DIRECTIVE);
subsystemContent.append(":=");
subsystemContent.append(startOrder);
}
} catch (Exception ex) {
// apparently not a valid JarFile
}
ZipEntry zoe = new ZipEntry(artifactName.substring(idx));
try {
zos.putNextEntry(zoe);
Files.copy(tempPath, zos);
} finally {
zos.closeEntry();
tempArtifact.delete();
}
}
}
private static class SubsystemData {
File file;
Manifest manifest;
}
static class DeleteOnCloseFileInputStream extends FileInputStream {
final File file;
public DeleteOnCloseFileInputStream(File f) throws FileNotFoundException {
super(f);
file = f;
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
file.delete();
}
}
}
}