blob: ec187a50af109c0b6880132bbeeff79cee4d1011 [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.deployer.kar;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class KarArtifactInstaller implements ArtifactInstaller {
public static final String FEATURES_CLASSIFIER = "features";
private final Logger logger = LoggerFactory.getLogger(KarArtifactInstaller.class);
private static final String KAR_SUFFIX = ".kar";
private static final String ZIP_SUFFIX = ".zip";
private String base = "./";
private String localRepoPath = "./target/local-repo";
private String timestampPath;
private DocumentBuilderFactory dbf;
private FeaturesService featuresService;
public void init() {
dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
timestampPath = localRepoPath + File.separator + ".timestamps";
if (new File(timestampPath).mkdirs()) {
logger.warn("Unable to create directory for Karaf Archive timestamps. Results may vary...");
}
logger.info("Karaf archives will be extracted to {}", localRepoPath);
logger.info("Timestamps for Karaf archives will be extracted to {}", timestampPath);
}
public void destroy() {
logger.info("Karaf archive installer destroyed.");
}
public void install(File file) throws Exception {
// Check to see if this file has already been extracted. For example, on restart of Karaf,
// we don't necessarily want to re-extract all the Karaf Archives!
//
if (alreadyExtracted(file)) {
logger.info("Ignoring '{}'; timestamp indicates it's already been deployed.", file);
return;
}
logger.info("Installing KAR file {}", file);
ZipFile zipFile = new ZipFile(file);
List<URI> featuresRepositoriesInKar = new ArrayList<URI>();
Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String repoEntryName = getRepoEntryName(entry);
if (repoEntryName != null) {
File extract = extract(zipFile, entry, repoEntryName, localRepoPath);
if (isFeaturesRepository(extract)) {
addToFeaturesRepositories(extract.toURI());
featuresRepositoriesInKar.add(extract.toURI());
}
}
if (entry.getName().startsWith("resource")) {
String resourceEntryName = entry.getName().substring("resource/".length());
extract(zipFile, entry, resourceEntryName, base);
}
}
installFeatures(featuresRepositoriesInKar);
zipFile.close();
updateTimestamp(file);
}
private void installFeatures(List<URI> featuresRepositories) {
for (Repository repository : featuresService.listRepositories()) {
for (URI karFeatureRepoUri : featuresRepositories) {
if (repository.getURI().equals(karFeatureRepoUri)) {
try {
for (Feature feature : repository.getFeatures()) {
try {
featuresService.installFeature(feature, EnumSet.noneOf(FeaturesService.Option.class));
} catch (Exception e) {
logger.warn("Unable to install Kar feature {}", feature.getName() + "/" + feature.getVersion(), e);
}
}
} catch (Exception e) {
logger.warn("Can't get features for KAR {}", karFeatureRepoUri, e);
}
}
}
}
}
private File extract(ZipFile zipFile, ZipEntry entry, String repoEntryName, String base) throws IOException {
File extract;
if (entry.isDirectory()) {
extract = new File(base + File.separator + repoEntryName);
logger.debug("Creating directory '{}'", extract.getName());
extract.mkdirs();
} else {
extract = new File(base + File.separator + repoEntryName);
extract.getParentFile().mkdirs();
InputStream in = zipFile.getInputStream(entry);
FileOutputStream out = new FileOutputStream(extract);
byte[] buffer = new byte[8192];
int count = in.read(buffer);
int totalBytes = 0;
while (count >= 0) {
out.write(buffer, 0, count);
totalBytes += count;
count = in.read(buffer);
}
logger.debug("Extracted {} bytes to {}", totalBytes, extract);
in.close();
out.flush();
out.close();
}
return extract;
}
private String getRepoEntryName(ZipEntry entry) {
String entryName = entry.getName();
if (entryName.startsWith("repository")) {
return entryName.substring("repository/".length());
}
if (entryName.startsWith("META-INF") || entryName.startsWith("resources")) {
return null;
}
return entryName;
}
public void uninstall(File file) throws Exception {
File timestamp = getArchiveTimestampFile(file);
if (timestamp.exists()) {
logger.debug("Removing the timestamp file");
timestamp.delete();
}
logger.warn("Karaf archive '{}' has been removed; however, its feature URLs have not been deregistered, and its bundles are still available in '{}'.", file, localRepoPath);
}
public void update(File file) throws Exception {
logger.warn("Karaf archive '{}' has been updated; redeploying.", file);
install(file);
}
protected void updateTimestamp(File karafArchive) throws Exception {
File timestamp = getArchiveTimestampFile(karafArchive);
if (timestamp.exists()) {
logger.debug("Deleting old timestamp file '{}'", timestamp);
if (!timestamp.delete()) {
throw new Exception("Unable to delete archive timestamp '" + timestamp + "'");
}
}
logger.debug("Creating timestamp file '{}'", timestamp);
timestamp.createNewFile();
}
protected boolean alreadyExtracted(File karafArchive) {
File timestamp = getArchiveTimestampFile(karafArchive);
if (timestamp.exists()) {
return timestamp.lastModified() >= karafArchive.lastModified();
}
return false;
}
protected File getArchiveTimestampFile(File karafArchive) {
File timestampDir = new File(new File(localRepoPath), ".timestamps");
if (!timestampDir.exists()) {
timestampDir.mkdirs();
}
return new File(timestampDir, karafArchive.getName());
}
protected boolean isFeaturesRepository(File artifact) {
try {
if (artifact.isFile() && artifact.getName().endsWith(".xml")) {
Document doc = parse(artifact);
String name = doc.getDocumentElement().getLocalName();
String uri = doc.getDocumentElement().getNamespaceURI();
if ("features".equals(name) && (uri == null || "".equals(uri) || uri.startsWith("http://karaf.apache.org/xmlns/features/v"))) {
return true;
}
}
} catch (Exception e) {
logger.debug("File '{}' is not a features file.", artifact.getName(), e);
}
return false;
}
protected Document parse(File artifact) throws Exception {
DocumentBuilder db = dbf.newDocumentBuilder();
db.setErrorHandler(new ErrorHandler() {
public void warning(SAXParseException exception) throws SAXException {
}
public void error(SAXParseException exception) throws SAXException {
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
});
return db.parse(artifact);
}
private void addToFeaturesRepositories(URI uri) {
// URI mvnUri = pathToMvnUri(path);
try {
featuresService.removeRepository(uri);
featuresService.addRepository(uri);
logger.info("Added feature repository '{}'.", uri);
} catch (Exception e) {
logger.warn("Unable to add repository '{}'", uri, e);
}
}
static URI pathToMvnUri(String path) {
String[] bits = path.split("/");
String classifier = FEATURES_CLASSIFIER;
String artifactType = "xml";
String version = bits[bits.length - 2];
String artifactId = bits[bits.length - 3];
StringBuilder buf = new StringBuilder("mvn:");
for (int i = 0; i < bits.length - 3; i++) {
buf.append(bits[i]);
if (i < bits.length - 4) {
buf.append(".");
}
}
buf.append("/").append(artifactId).append("/").append(version).append("/").append(artifactType).append("/").append(classifier);
URI mvnUri = URI.create(buf.toString());
return mvnUri;
}
public boolean canHandle(File file) {
// If the file ends with .kar, then we can handle it!
//
if (file.isFile() && file.getName().endsWith(KAR_SUFFIX)) {
logger.info("Found a .kar file to deploy.");
return true;
}
// Otherwise, check to see if it's a zip file containing a META-INF/KARAF.MF manifest.
//
else if (file.isFile() && file.getName().endsWith(ZIP_SUFFIX)) {
logger.debug("Found a .zip file to deploy; checking contents to see if it's a Karaf archive.");
try {
if (new ZipFile(file).getEntry("META-INF/KARAF.MF") != null) {
logger.info("Found a Karaf archive with .zip prefix; will deploy.");
return true;
}
} catch (Exception e) {
logger.warn("Problem extracting zip file '{}'; ignoring.", file.getName(), e);
}
}
return false;
}
public boolean deleteLocalRepository() {
return deleteDirectory(new File(localRepoPath));
}
private boolean deleteDirectory(File path) {
if (path.exists()) {
File[] files = path.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
}
return (path.delete());
}
public void setBasePath(String base) {
this.base = base;
}
public void setLocalRepoPath(String localRepoPath) {
this.localRepoPath = localRepoPath;
}
public void setFeaturesService(FeaturesService featuresService) {
this.featuresService = featuresService;
}
}