/* * 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.features.command; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import org.apache.karaf.features.BundleInfo; import org.apache.karaf.features.Dependency; import org.apache.karaf.features.Feature; import org.apache.karaf.features.FeaturesService; import org.apache.karaf.features.command.completers.AvailableFeatureCompleter; import org.apache.karaf.shell.api.action.Argument; import org.apache.karaf.shell.api.action.Command; import org.apache.karaf.shell.api.action.Completion; import org.apache.karaf.shell.api.action.Option; import org.apache.karaf.shell.api.action.lifecycle.Reference; import org.apache.karaf.shell.api.action.lifecycle.Service; import org.apache.karaf.shell.support.completers.FileCompleter; import org.ops4j.pax.url.mvn.MavenResolver; /** * Simple {@link FeaturesCommandSupport} implementation that allows a user in * the karaf shell to export the bundles associated with a given feature to the * file system. This is useful for several use cases, such as in the event you * need to deploy the functionality offered by a particular feature to an OBR * repository. * */ @Service @Command(scope = "feature", name = "export-bundles", description = "Export all of the bundles that make up a specified feature to a directory on the file system.") public class FeatureExport extends FeaturesCommandSupport { /** * Inject a {@link MavenResolver} so we can translate from a * {@link BundleInfo} in a {@link Feature} into the raw bundle from maven. */ @Reference private MavenResolver resolver; /** * The name of the feature you want to export. */ @Argument(index = 0, name = "featureName", description = "The name of the feature you want to export bundles for", required = true, multiValued = false) @Completion(value = AvailableFeatureCompleter.class) private String featureName = null; /** * The location we'll export the bundles to. */ @Argument(index = 1, name = "exportLocation", description = "Where you want to export the bundles", multiValued = false, required = true) @Completion(value = FileCompleter.class) private String exportLocation; /** * The version of the feature you want to export. */ @Option(name = "-v", multiValued = false, aliases = { "--version" }, description = "The version of the feature you want to export bundles for. Default is latest", required = false) private String featureVersion = null; /** * Option indicating that only bundles marked as a dependency should be * exported. */ @Option(name = "-d", multiValued = false, aliases = { "--dependencies-only" }, description = "This flag indicates that only bundles marked as a dependency will be exported.", required = false) private boolean onlyDependencies = false; /** * {@inheritDoc} */ @Override public void doExecute(final FeaturesService featuresService) throws Exception { if (resolver == null) { throw new IllegalStateException("No maven resolver implementation found."); } else { final File destination = new File(exportLocation); if (!prepareDestination(destination)) { System.err.println("Invalid exportLocation specified: " + exportLocation); } else { final Feature feature = featureVersion != null ? featuresService.getFeature(featureName, featureVersion) : featuresService.getFeature(featureName); if (feature == null) { System.err.println("Could not find specified feature: '" + featureName + "' version '" + featureVersion + "'"); } else { // Save feature content bundles. saveBundles(destination, feature, featuresService); } } } } /** * Prepare the target destination directory. * * @param destination * Where we'll save the bundles * @return true if it is valid, false otherwise */ private boolean prepareDestination(final File destination) { return (destination.isDirectory() || destination.mkdirs()); } /** * Save the feature bundles, and all of its transitive dependency bundles. * * @param dest * The target directory where we'll save the feature bundles * @param feature * The {@link Feature} we're saving * @throws Exception * If there is an issue saving the bundles or resolving the * feature */ private void saveBundles(final File dest, final Feature feature, final FeaturesService featuresService) throws Exception { // Save this feature's bundles. for (final BundleInfo info : feature.getBundles()) { if (!onlyDependencies || (onlyDependencies && info.isDependency())) { final File resolvedLocation = resolver.resolve(info.getLocation()); if (copyFileToDirectory(resolvedLocation, dest)) { System.out.println("Exported '" + feature.getName() + "/" + feature.getVersion() + "' bundle: " + info.getLocation()); } else { System.out.println("Already exported bundle: " + info.getLocation()); } } } // Save feature's dependency bundles. for (final Dependency dependency : feature.getDependencies()) { final Feature dFeature = featuresService.getFeature(dependency.getName(), dependency.getVersion()); if (dFeature != null) { saveBundles(dest, dFeature, featuresService); } else { System.err.println("Unable to resolve dependency feature! '" + dependency.getName() + "' '" + dependency.getVersion() + "'"); throw new Exception("Unable to resolve dependency feature '" + dependency.getName() + "/" + dependency.getVersion() + "' while exporting '" + featureName + "/" + featureVersion + "'"); } } } /** * Simple method to copy a file to a target destination directory. * * @param file * The file to copy * @param directory * The directory to copy it to * @return true if successful, false if it wasn't * @throws FileNotFoundException * If the file specified doesn't exist * @throws IOException * If there is an issue performing the copy */ private static boolean copyFileToDirectory(final File file, final File directory) throws FileNotFoundException, IOException { if (!directory.isDirectory()) { throw new IOException("Can't copy to non-directory specified: " + directory.getAbsolutePath()); } else { boolean copied = false; final File newFile = new File(directory.getAbsolutePath() + "/" + file.getName()); if (!newFile.isFile()) { try (final FileInputStream fis = new FileInputStream(file)) { try (final FileOutputStream fos = new FileOutputStream(newFile)) { byte[] buffer = new byte[1024 * 8]; int read = -1; while ((read = fis.read(buffer)) >= 0) { fos.write(buffer, 0, read); } } } copied = true; } return copied; } } }