/*
* 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.core;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.sling.crankstart.api.CrankstartCommand;
import org.apache.sling.crankstart.api.CrankstartCommandLine;
import org.apache.sling.crankstart.api.CrankstartConstants;
import org.apache.sling.crankstart.api.CrankstartContext;
import org.apache.sling.crankstart.api.CrankstartException;
import org.apache.sling.crankstart.core.commands.Configure;
import org.apache.sling.crankstart.core.commands.Defaults;
import org.apache.sling.crankstart.core.commands.Exit;
import org.apache.sling.crankstart.core.commands.InstallBundle;
import org.apache.sling.crankstart.core.commands.Log;
import org.apache.sling.crankstart.core.commands.NullCommand;
import org.apache.sling.crankstart.core.commands.SetOsgiFrameworkProperty;
import org.apache.sling.crankstart.core.commands.StartBundles;
import org.apache.sling.crankstart.core.commands.StartFramework;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Process a crankstart file */
public class CrankstartFileProcessor implements Callable<Object> {
private final CrankstartContext crankstartContext;
private final Logger log = LoggerFactory.getLogger(getClass());
private List<CrankstartCommand> builtinCommands = new ArrayList<CrankstartCommand>();
private List<CrankstartCommand> extensionCommands = new ArrayList<CrankstartCommand>();
public CrankstartFileProcessor() {
this(new CrankstartContext());
}
public CrankstartFileProcessor(CrankstartContext ctx) {
this.crankstartContext = ctx;
builtinCommands.add(new InstallBundle());
builtinCommands.add(new Log());
builtinCommands.add(new SetOsgiFrameworkProperty());
builtinCommands.add(new StartBundles());
builtinCommands.add(new StartFramework());
builtinCommands.add(new Configure());
builtinCommands.add(new Defaults());
builtinCommands.add(new Exit());
// Need a null "classpath" command as our launcher uses it
// outside of the usual command mechanism - it shouldn't cause
// an error in the normal processing
builtinCommands.add(new NullCommand("classpath", "set the classpath used by the Crankstart launcher"));
log.info("{} ready, built-in commands: {}", getClass().getSimpleName(), getDescriptions(builtinCommands));
}
public Object call() throws Exception {
final String inputFilename = System.getProperty(CrankstartConstants.CRANKSTART_INPUT_FILENAME);
if(inputFilename == null) {
throw new CrankstartException("Missing system property " + CrankstartConstants.CRANKSTART_INPUT_FILENAME);
}
process(new FileReader(new File(inputFilename)));
if(crankstartContext.getAttribute(CrankstartContext.ATTR_STOP_OSGI_FRAMEWORK) != null) {
log.info("Stopping OSGi framework due to {} attribute (state={})",
CrankstartContext.ATTR_STOP_OSGI_FRAMEWORK,
crankstartContext.getOsgiFramework().getState()
);
crankstartContext.getOsgiFramework().stop();
System.exit(0);
} else {
waitForExit();
}
return null;
}
/** Execute supplied command line with all commands that accept it, and return
* the number of commands that did.
*/
private int execute(CrankstartCommandLine cmdLine, Collection<CrankstartCommand> candidateCommands) throws Exception {
int executed = 0;
for(CrankstartCommand cmd : candidateCommands) {
if(cmd.appliesTo(cmdLine)) {
cmd.execute(crankstartContext, cmdLine);
executed++;
}
}
return executed;
}
public void process(Reader input) throws Exception {
final CrankstartParserImpl parser = new CrankstartParserImpl() {
@Override
protected String getVariable(String name) {
String result = System.getProperty(name);
if(result == null) {
result = crankstartContext.getDefaults().get(name);
}
if(result == null) {
result = super.getVariable(name);
}
if(result != null) {
result = result.trim();
}
return result;
}
};
final Iterator<CrankstartCommandLine> it = parser.parse(input);
while(it.hasNext()) {
final CrankstartCommandLine cmdLine = it.next();
// First try the built-in commands
// If no command found try existing extension commands
// If no command found reload extension commands and try them again
// If no command found fail
if(execute(cmdLine, builtinCommands) == 0) {
if(execute(cmdLine, extensionCommands) == 0) {
reloadExtensionCommands();
if(execute(cmdLine, extensionCommands) == 0) {
throw new CrankstartException(
"Invalid command '" + cmdLine.getVerb()
+ "', built-in commands:" + getDescriptions(builtinCommands)
+ ", extension commands: " + getDescriptions(extensionCommands)
);
}
}
}
if(crankstartContext.getAttribute(CrankstartContext.ATTR_STOP_CRANKSTART_PROCESSING) != null) {
log.info("{} attribute is set, ignoring the remaining crankstart commands", CrankstartContext.ATTR_STOP_CRANKSTART_PROCESSING);
break;
}
}
}
/** Reload our extension commands from the OSGi framework
* @throws InvalidSyntaxException */
@SuppressWarnings({ "rawtypes", "unchecked" })
private synchronized void reloadExtensionCommands() throws InvalidSyntaxException {
if(crankstartContext.getOsgiFramework() == null) {
return;
}
extensionCommands.clear();
final BundleContext bc = crankstartContext.getOsgiFramework().getBundleContext();
final ServiceReference [] refs = bc.getServiceReferences(CrankstartCommand.class.getName(), null);
if(refs != null) {
for(ServiceReference ref : refs) {
extensionCommands.add((CrankstartCommand)bc.getService(ref));
}
}
log.info("Reloaded extension commands: {}", getDescriptions(extensionCommands));
}
public void waitForExit() throws InterruptedException {
if(crankstartContext.getOsgiFramework() == null) {
throw new IllegalStateException("OSGi framework not started");
}
log.info("Waiting for OSGi framework to exit...");
crankstartContext.getOsgiFramework().waitForStop(0);
log.info("OSGi framework exited");
}
private String getDescriptions(Collection<CrankstartCommand> commands) throws CrankstartException {
final StringBuilder sb = new StringBuilder();
for(CrankstartCommand cmd : commands) {
if(sb.length() > 0) {
sb.append(", ");
}
sb.append(cmd.getDescription());
}
if(sb.length() == 0) {
sb.append("<none>");
}
return sb.toString();
}
}