blob: 5607f2dd6635c74b225c8f9b6e9f188cf9681e87 [file] [log] [blame]
/*
* 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.feature.launcher.impl;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.builder.BuilderContext;
import org.apache.sling.feature.builder.FeatureBuilder;
import org.apache.sling.feature.builder.MergeHandler;
import org.apache.sling.feature.builder.PostProcessHandler;
import org.apache.sling.feature.io.IOUtils;
import org.apache.sling.feature.io.artifacts.ArtifactHandler;
import org.apache.sling.feature.io.artifacts.ArtifactManager;
import org.apache.sling.feature.io.json.FeatureJSONReader;
import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler;
import org.slf4j.Logger;
public class FeatureProcessor {
/**
* Initialize the launcher
* Read the features and prepare the application
* @param config The current configuration
* @param artifactManager The artifact manager
* @param loadedFeatures This map will be populated with features that were loaded as part of this process
* @return The merged feature representing the application
* @throws IOException when an IO exception occurs during application creation
*/
public static Feature createApplication(final Logger logger, final LauncherConfig config,
final ArtifactManager artifactManager, final Map<ArtifactId, Feature> loadedFeatures) throws IOException
{
final BuilderContext builderContext = new BuilderContext(id -> {
try {
final ArtifactHandler handler = artifactManager.getArtifactHandler(id.toMvnUrl());
try (final Reader r = new InputStreamReader(handler.getLocalURL().openStream(), "UTF-8")) {
final Feature f = FeatureJSONReader.read(r, handler.getUrl());
return f;
}
} catch (IOException e) {
// ignore
return null;
}
});
builderContext.setArtifactProvider(id -> {
try {
final ArtifactHandler handler = artifactManager.getArtifactHandler(id.toMvnUrl());
return handler.getLocalURL();
} catch (final IOException e) {
// ignore
return null;
}
});
builderContext.addArtifactsOverrides(config.getArtifactClashOverrides());
builderContext.addVariablesOverrides(config.getVariables());
builderContext.addFrameworkPropertiesOverrides(config.getInstallation().getFrameworkProperties());
builderContext.addMergeExtensions(StreamSupport.stream(Spliterators.spliteratorUnknownSize(
ServiceLoader.load(MergeHandler.class).iterator(), Spliterator.ORDERED), false)
.toArray(MergeHandler[]::new));
builderContext.addPostProcessExtensions(StreamSupport.stream(Spliterators.spliteratorUnknownSize(
ServiceLoader.load(PostProcessHandler.class).iterator(), Spliterator.ORDERED), false)
.toArray(PostProcessHandler[]::new));
for (Map.Entry<String, Map<String,String>> entry : config.getExtensionConfiguration().entrySet()) {
builderContext.setHandlerConfiguration(entry.getKey(), entry.getValue());
}
List<Feature> features = new ArrayList<>();
for (final String featureFile : config.getFeatureFiles()) {
for (final String initFile : IOUtils.getFeatureFiles(config.getHomeDirectory(), featureFile)) {
logger.debug("Reading feature file {}", initFile);
final ArtifactHandler featureArtifact = artifactManager.getArtifactHandler(initFile);
try (final Reader r = new InputStreamReader(featureArtifact.getLocalURL().openStream(), "UTF-8")) {
final Feature f = FeatureJSONReader.read(r, featureArtifact.getUrl());
loadedFeatures.put(f.getId(), f);
features.add(f);
} catch (Exception ex) {
throw new IOException("Error reading feature: " + initFile, ex);
}
}
}
// TODO make feature id configurable
final Feature app = FeatureBuilder.assemble(ArtifactId.fromMvnId("group:assembled:1.0.0"), builderContext, features.toArray(new Feature[0]));
loadedFeatures.put(app.getId(), app);
// TODO: this sucks
for (Artifact bundle : app.getBundles()) {
if ( bundle.getStartOrder() == 0) {
final int so = bundle.getMetadata().get("start-level") != null ? Integer.parseInt(bundle.getMetadata().get("start-level")) : 1;
bundle.setStartOrder(so);
}
}
FeatureBuilder.resolveVariables(app, config.getVariables());
return app;
}
/**
* Prepare the launcher
* - add all bundles to the bundle map of the installation object
* - add all other artifacts to the install directory (only if startup mode is INSTALL)
* - process configurations
* @param ctx The launcher prepare context
* @param config The launcher configuration
* @param app The merged feature to launch
* @param loadedFeatures The features previously loaded by the launcher, this includes features that
* were passed in via file:// URLs from the commandline
* @throws Exception when something goes wrong
*/
public static void prepareLauncher(final LauncherPrepareContext ctx, final LauncherConfig config,
final Feature app, Map<ArtifactId, Feature> loadedFeatures) throws Exception {
for(final Map.Entry<Integer, List<Artifact>> entry : app.getBundles().getBundlesByStartOrder().entrySet()) {
for(final Artifact a : entry.getValue()) {
final URL artifactFile = ctx.getArtifactFile(a.getId());
config.getInstallation().addBundle(entry.getKey(), artifactFile);
}
}
for (final Configuration cfg : app.getConfigurations()) {
if (Configuration.isFactoryConfiguration(cfg.getPid())) {
config.getInstallation().addConfiguration(Configuration.getName(cfg.getPid()),
Configuration.getFactoryPid(cfg.getPid()), cfg.getConfigurationProperties());
} else {
config.getInstallation().addConfiguration(cfg.getPid(), null, cfg.getConfigurationProperties());
}
}
for (final Map.Entry<String, String> prop : app.getFrameworkProperties().entrySet()) {
if ( !config.getInstallation().getFrameworkProperties().containsKey(prop.getKey()) ) {
config.getInstallation().getFrameworkProperties().put(prop.getKey(), prop.getValue());
}
}
extensions: for(final Extension ext : app.getExtensions()) {
Iterator<ExtensionHandler> i = ServiceLoader.load(ExtensionHandler.class, FeatureProcessor.class.getClassLoader()).iterator();
// Stream the iterator, sort them based on Priority in reversed order and then collection into a list
List<ExtensionHandler> prioritizedExtensionHandlerList =
StreamSupport
.stream(Spliterators.spliteratorUnknownSize(i, Spliterator.ORDERED), false)
.sorted(Comparator.comparingInt(ExtensionHandler::getPriority).reversed())
.collect(Collectors.toList());
for (ExtensionHandler handler : prioritizedExtensionHandlerList)
{
if (handler.handle(new ExtensionContextImpl(ctx, config.getInstallation(), loadedFeatures), ext)) {
continue extensions;
}
}
if ( ext.isRequired() ) {
throw new Exception("Unknown required extension " + ext.getName());
}
}
}
}