blob: 3a0e043bc1f9ce5a7d85798836d66450a68a2960 [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.crankstart.launcher;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import org.apache.sling.provisioning.model.Artifact;
import org.apache.sling.provisioning.model.ArtifactGroup;
import org.apache.sling.provisioning.model.Feature;
import org.apache.sling.provisioning.model.Model;
import org.apache.sling.provisioning.model.ModelUtility;
import org.apache.sling.provisioning.model.ModelUtility.VariableResolver;
import org.apache.sling.provisioning.model.RunMode;
import org.apache.sling.provisioning.model.io.ModelReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Launch an OSGi app instance using the Sling provisioning model */
public class Launcher {
private Model model = new Model();
private static final Logger log = LoggerFactory.getLogger(Launcher.class);
public static final String CRANKSTART_FEATURE = ":crankstart";
public static final String MODEL_KEY = "model";
public static final String LISTENER_KEY = "listener";
public static final String FRAMEWORK_KEY = "framework";
public static final String VARIABLE_OVERRIDE_PREFIX = "crankstart.model.";
/** Default variable resolver using system properties */
public static final VariableResolver DEFAULT_VARIABLE_RESOLVER =
new PropertiesVariableResolver(System.getProperties(), VARIABLE_OVERRIDE_PREFIX) {
@Override
protected void onOverride(String variableName, String value, String propertyName) {
log.info("Overriding model variable {}={} (from system property {})", variableName, value, propertyName);
}
};
/** Allow for overriding model variables */
private VariableResolver variableResolver;
public static final FeatureFilter NOT_CRANKSTART_FILTER = new FeatureFilter() {
@Override
public boolean ignoreFeature(Feature f) {
return Launcher.CRANKSTART_FEATURE.equals(f.getName());
}
};
public static final FeatureFilter ONLY_CRANKSTART_FILTER = new FeatureFilter() {
@Override
public boolean ignoreFeature(Feature f) {
return !Launcher.CRANKSTART_FEATURE.equals(f.getName());
}
};
private LauncherListener listener;
public Launcher(String ... args) throws Exception {
MavenResolver.setup();
withVariableResolver(null);
withModelPaths(args);
}
/** Use the supplied VariableResolver. Defaults to DEFAULT_VARIABLE_RESOLVER if v
* is null or if this is not called.
*/
public Launcher withVariableResolver(VariableResolver v) {
variableResolver = (v == null ? DEFAULT_VARIABLE_RESOLVER : v);
return this;
}
public Launcher withListener(LauncherListener listener) {
this.listener = listener;
return this;
}
/** Add models from the supplied paths, can be either files or folders */
public Launcher withModelPaths(String ... paths) throws Exception {
// Find all files to read and sort the list, to be deterministic
final SortedSet<File> toRead = new TreeSet<File>();
for(String name : paths) {
final File f = new File(name);
if(f.isDirectory()) {
final String [] list = f.list();
for(String s : list) {
toRead.add(new File(f, s));
}
} else {
toRead.add(f);
}
}
// Merge all model files
for(File f : toRead) {
mergeModel(f);
}
computeEffectiveModel();
return this;
}
public void computeEffectiveModel() throws Exception {
new NestedModelsMerger(model).visit();
model = ModelUtility.getEffectiveModel(model, variableResolver);
}
public Model getModel() {
return model;
}
/** Can be called before launch() to read and merge additional models.
* @param r provisioning model to read, closed by this method after reading */
public static void mergeModel(Model mergeInto, Reader r, String sourceInfo) throws IOException {
log.info("Merging provisioning model {}", sourceInfo);
try {
final Model m = ModelReader.read(r, sourceInfo);
ModelUtility.merge(mergeInto, m);
} finally {
r.close();
}
}
/** Can be called before launch() to read and merge additional models */
public void mergeModel(File f) throws IOException {
mergeModel(model, new BufferedReader(new FileReader(f)), f.getAbsolutePath());
}
public void launch() throws Exception {
// Setup initial classpath to launch the OSGi framework
for(URL u : getClasspathURLs(model, CRANKSTART_FEATURE)) {
addToClasspath(u);
}
// Need to load FrameworkSetup in this way to avoid any static references to OSGi classes in this class, as those are
// not available yet when this class is loaded.
final Callable<?> c = (Callable<?>) getClass().getClassLoader().loadClass("org.apache.sling.crankstart.launcher.FrameworkSetup").newInstance();
@SuppressWarnings("unchecked") final Map<String, Object> cmap = (Map<String, Object>)c;
cmap.put(MODEL_KEY, model);
cmap.put(LISTENER_KEY, listener);
c.call();
}
/** Slightly hacky way to add URLs to the system classloader,
* based on http://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
*/
private void addToClasspath(URL u) throws IOException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
final URLClassLoader sysLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
final Method m = URLClassLoader.class.getDeclaredMethod("addURL",new Class[]{URL.class});
m.setAccessible(true);
m.invoke(sysLoader,new Object[]{ u });
log.info("Added to classpath: {}", u);
}
/** Convert a Model feature to a set of URLs meant to setup
* an URLClassLoader.
*/
List<URL> getClasspathURLs(Model m, String featureName) throws MalformedURLException {
final List<URL> result = new ArrayList<URL>();
// Add all URLs from the special feature to our classpath
final Feature f = m.getFeature(featureName);
if(f == null) {
log.warn("No {} feature found in provisioning model, nothing to add to our classpath", featureName);
} else {
for(RunMode rm : f.getRunModes()) {
for(ArtifactGroup g : rm.getArtifactGroups()) {
for(Artifact a : g) {
final String url = MavenResolver.mvnUrl(a);
try {
result.add(new URL(url));
} catch(MalformedURLException e) {
final MalformedURLException up = new MalformedURLException("Invalid URL [" + url + "]");
up.initCause(e);
throw up;
}
}
}
}
}
return result;
}
public static void main(String [] args) throws Exception {
if(args.length < 1) {
System.err.println("Usage: " + Launcher.class.getSimpleName() + " provisioning-model [provisioning-model ...]");
System.err.println("Where provisioning-model is either a Sling provisioning model file");
System.err.println("or a folder that contains several of those.");
System.exit(0);
}
new Launcher(args).launch();
}
}