blob: ae439fe227a0b986573da28c808cd2ac72a7ae6b [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.karaf.tooling;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import org.apache.karaf.features.BootFinished;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.main.Main;
import org.apache.karaf.tooling.utils.MojoSupport;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Files;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Run a Karaf instance
*/
@Mojo(name = "run", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = false)
public class RunMojo extends MojoSupport {
/**
* Directory containing Karaf container base directory.
*/
@Parameter(defaultValue = "${project.build.directory}/karaf")
private File karafDirectory = null;
/**
* Location where to download the Karaf distribution
*/
@Parameter(defaultValue = "mvn:org.apache.karaf/apache-karaf/LATEST/zip")
private String karafDistribution = null;
/**
* Define if the project artifact should be deployed in the started container or not
*/
@Parameter(defaultValue = "true")
private boolean deployProjectArtifact = true;
/**
* A list of URLs referencing feature repositories that will be added
* to the karaf instance started by this goal.
*/
@Parameter
private String[] featureRepositories = null;
/**
* Comma-separated list of features to install.
*/
@Parameter(defaultValue = "")
private String featuresToInstall = null;
/**
* Define if the Karaf container keep running or stop just after the goal execution
*/
@Parameter(defaultValue = "true")
private boolean keepRunning = true;
/**
* Define if the Karaf embedded sshd should be started or not
*/
@Parameter(defaultValue = "false")
private String startSsh = "false";
private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
public void execute() throws MojoExecutionException, MojoFailureException {
if (karafDirectory.exists()) {
getLog().info("Using Karaf container located " + karafDirectory.getAbsolutePath());
} else {
getLog().info("Extracting Karaf container");
try {
File karafArchiveFile = resolveFile(karafDistribution);
extract(karafArchiveFile, karafDirectory);
} catch (Exception e) {
throw new MojoFailureException("Can't extract Karaf container", e);
}
}
getLog().info("Starting Karaf container");
System.setProperty("karaf.home", karafDirectory.getAbsolutePath());
System.setProperty("karaf.base", karafDirectory.getAbsolutePath());
System.setProperty("karaf.data", karafDirectory.getAbsolutePath() + "/data");
System.setProperty("karaf.etc", karafDirectory.getAbsolutePath() + "/etc");
System.setProperty("karaf.log", karafDirectory.getAbsolutePath() + "/data/log");
System.setProperty("karaf.instances", karafDirectory.getAbsolutePath() + "/instances");
System.setProperty("karaf.startLocalConsole", "false");
System.setProperty("karaf.startRemoteShell", startSsh);
System.setProperty("karaf.lock", "false");
Main main = new Main(new String[0]);
try {
main.launch();
while (main.getFramework().getState() != Bundle.ACTIVE) {
Thread.sleep(1000);
}
BundleContext bundleContext = main.getFramework().getBundleContext();
Object bootFinished = null;
while (bootFinished == null) {
Thread.sleep(1000);
ServiceReference ref = bundleContext.getServiceReference(BootFinished.class);
if (ref != null) {
bootFinished = bundleContext.getService(ref);
}
}
Object featureService = findFeatureService(bundleContext);
addFeatureRepositories(featureService);
deploy(bundleContext, featureService);
addFeatures(featureService);
if (keepRunning)
main.awaitShutdown();
main.destroy();
} catch (Throwable e) {
throw new MojoExecutionException("Can't start container", e);
} finally {
System.gc();
}
}
void addFeatureRepositories(Object featureService) throws MojoExecutionException {
if (featureRepositories != null) {
try {
Class<? extends Object> serviceClass = featureService.getClass();
Method addRepositoryMethod = serviceClass.getMethod("addRepository", URI.class);
for (String featureRepo : featureRepositories) {
addRepositoryMethod.invoke(featureService, URI.create(featureRepo));
}
} catch (Exception e) {
throw new MojoExecutionException("Failed to add feature repositories to karaf", e);
}
}
}
void deploy(BundleContext bundleContext, Object featureService) throws MojoExecutionException {
if (deployProjectArtifact) {
File artifact = project.getArtifact().getFile();
File attachedFeatureFile = getAttachedFeatureFile(project);
boolean artifactExists = artifact != null && artifact.exists();
boolean attachedFeatureFileExists = attachedFeatureFile != null && attachedFeatureFile.exists();
if (artifactExists) {
if (project.getPackaging().equals("bundle")) {
try {
Bundle bundle = bundleContext.installBundle(artifact.toURI().toURL().toString());
bundle.start();
} catch (Exception e) {
throw new MojoExecutionException("Can't deploy project artifact in container", e);
}
} else {
throw new MojoExecutionException("Packaging " + project.getPackaging() + " is not supported");
}
} else if (attachedFeatureFileExists) {
addFeaturesAttachmentAsFeatureRepository(featureService, attachedFeatureFile);
} else {
throw new MojoExecutionException("Project artifact doesn't exist");
}
}
}
void addFeatures(Object featureService) throws MojoExecutionException {
if (featuresToInstall != null) {
try {
Class<? extends Object> serviceClass = featureService.getClass();
Method installFeatureMethod = serviceClass.getMethod("installFeature", String.class);
String[] features = featuresToInstall.split(" *, *");
for (String feature : features) {
installFeatureMethod.invoke(featureService, feature);
Thread.sleep(1000L);
}
} catch (Exception e) {
throw new MojoExecutionException("Failed to add features to karaf", e);
}
}
}
public static void extract(File sourceFile, File targetFolder) throws IOException {
if (sourceFile.getAbsolutePath().indexOf(".zip") > 0) {
extractZipDistribution(sourceFile, targetFolder);
} else if (sourceFile.getAbsolutePath().indexOf(".tar.gz") > 0) {
extractTarGzDistribution(sourceFile, targetFolder);
} else {
throw new IllegalStateException("Unknown packaging of distribution; only zip or tar.gz could be handled.");
}
return;
}
private static void extractTarGzDistribution(File sourceDistribution, File _targetFolder) throws IOException {
File uncompressedFile = Files.createTempFile("uncompressedTarGz-", ".tar").toFile();
extractGzArchive(new FileInputStream(sourceDistribution), uncompressedFile);
extract(new TarArchiveInputStream(new FileInputStream(uncompressedFile)), _targetFolder);
FileUtils.forceDelete(uncompressedFile);
}
private static void extractZipDistribution(File sourceDistribution, File _targetFolder) throws IOException {
extract(new ZipArchiveInputStream(new FileInputStream(sourceDistribution)), _targetFolder);
}
private static void extractGzArchive(InputStream tarGz, File tar) throws IOException {
BufferedInputStream in = new BufferedInputStream(tarGz);
FileOutputStream out = new FileOutputStream(tar);
GzipCompressorInputStream gzIn = new GzipCompressorInputStream(in);
final byte[] buffer = new byte[1000];
int n = 0;
while (-1 != (n = gzIn.read(buffer))) {
out.write(buffer, 0, n);
}
out.close();
gzIn.close();
}
private static void extract(ArchiveInputStream is, File targetDir) throws IOException {
try {
if (targetDir.exists()) {
FileUtils.forceDelete(targetDir);
}
targetDir.mkdirs();
ArchiveEntry entry = is.getNextEntry();
while (entry != null) {
String name = entry.getName();
name = name.substring(name.indexOf("/") + 1);
File file = new File(targetDir, name);
if (!file.getCanonicalPath().startsWith(targetDir.getCanonicalPath())) {
throw new IOException("Archive cannot contain paths with .. characters");
}
if (entry.isDirectory()) {
file.mkdirs();
}
else {
file.getParentFile().mkdirs();
OutputStream os = new FileOutputStream(file);
try {
IOUtils.copy(is, os);
}
finally {
IOUtils.closeQuietly(os);
}
}
entry = is.getNextEntry();
}
}
finally {
is.close();
}
}
protected static boolean isMavenUrl(String name) {
Matcher m = mvnPattern.matcher(name);
return m.matches();
}
private File resolveFile(String file) {
File fileResolved = null;
if (isMavenUrl(file)) {
fileResolved = new File(fromMaven(file));
try {
Artifact artifactTemp = resourceToArtifact(file, false);
if (!fileResolved.exists()) {
try {
artifactResolver.resolve(artifactTemp, remoteRepos, localRepo);
fileResolved = artifactTemp.getFile();
} catch (ArtifactResolutionException e) {
getLog().error("Artifact was not resolved", e);
} catch (ArtifactNotFoundException e) {
getLog().error("Artifact was not found", e);
}
}
} catch (MojoExecutionException e) {
getLog().error(e);
}
} else {
fileResolved = new File(file);
}
return fileResolved;
}
/**
* Return a path for an artifact:
* - if the input is already a path (doesn't contain ':'), the same path is returned.
* - if the input is a Maven URL, the input is converted to a default repository location path, type and classifier
* are optional.
*
* @param name artifact data
* @return path as supplied or a default Maven repository path
*/
private static String fromMaven(String name) {
Matcher m = mvnPattern.matcher(name);
if (!m.matches()) {
return name;
}
StringBuilder b = new StringBuilder();
b.append(m.group(1));
for (int i = 0; i < b.length(); i++) {
if (b.charAt(i) == '.') {
b.setCharAt(i, '/');
}
}
b.append("/"); // groupId
String artifactId = m.group(2);
String version = m.group(3);
String extension = m.group(5);
String classifier = m.group(7);
b.append(artifactId).append("/"); // artifactId
b.append(version).append("/"); // version
b.append(artifactId).append("-").append(version);
if (present(classifier)) {
b.append("-").append(classifier);
}
if (present(classifier)) {
b.append(".").append(extension);
} else {
b.append(".jar");
}
return b.toString();
}
private static boolean present(String part) {
return part != null && !part.isEmpty();
}
private File getAttachedFeatureFile(MavenProject project) {
List<Artifact> attachedArtifacts = project.getAttachedArtifacts();
for (Artifact artifact : attachedArtifacts) {
if ("features".equals(artifact.getClassifier()) && "xml".equals(artifact.getType())) {
return artifact.getFile();
}
}
return null;
}
@SuppressWarnings({ "rawtypes", "unchecked" }) Object findFeatureService(BundleContext bundleContext) {
// Use Object as the service type and use reflection when calling the service,
// because the returned services use the OSGi classloader
ServiceReference ref = bundleContext.getServiceReference(FeaturesService.class);
if (ref != null) {
Object featureService = bundleContext.getService(ref);
return featureService;
}
return null;
}
private void addFeaturesAttachmentAsFeatureRepository(Object featureService, File attachedFeatureFile) throws MojoExecutionException {
if (featureService != null) {
try {
Class<? extends Object> serviceClass = featureService.getClass();
Method addRepositoryMethod = serviceClass.getMethod("addRepository", URI.class);
addRepositoryMethod.invoke(featureService, attachedFeatureFile.toURI());
} catch (Exception e) {
throw new MojoExecutionException("Failed to register attachment as feature repository", e);
}
} else {
throw new MojoExecutionException("Failed to find the FeatureService when adding a feature repository");
}
}
}