/* * 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.felix.bundlerepository.impl; import org.apache.felix.bundlerepository.*; import org.apache.felix.service.command.Descriptor; import org.apache.felix.service.command.Parameter; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.Version; import java.io.*; import java.lang.reflect.Array; import java.net.URL; import java.net.URLConnection; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class ObrGogoCommand { private static final String REPO_ADD = "add"; private static final String REPO_REMOVE = "remove"; private static final String REPO_LIST = "list"; private static final String REPO_REFRESH = "refresh"; private static final char VERSION_SEPARATOR = '@'; private final BundleContext m_bc; private final RepositoryAdmin m_repositoryAdmin; public ObrGogoCommand(BundleContext bc, RepositoryAdmin repositoryAdmin) { m_bc = bc; m_repositoryAdmin = repositoryAdmin; } private RepositoryAdmin getRepositoryAdmin() { return m_repositoryAdmin; } @Descriptor("manage repositories") public void repos( @Descriptor("( add | list | refresh | remove )") String action, @Descriptor("space-delimited list of repository URLs") String[] args) throws IOException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; if (args.length > 0) { for (int i = 0; i < args.length; i++) { try { if (action.equals(REPO_ADD)) { ra.addRepository(args[i]); } else if (action.equals(REPO_REFRESH)) { ra.removeRepository(args[i]); ra.addRepository(args[i]); } else if (action.equals(REPO_REMOVE)) { ra.removeRepository(args[i]); } else { System.out.println("Unknown repository operation: " + action); } } catch (Exception ex) { ex.printStackTrace(System.err); } } } else { org.apache.felix.bundlerepository.Repository[] repos = ra.listRepositories(); if ((repos != null) && (repos.length > 0)) { for (int i = 0; i < repos.length; i++) { System.out.println(repos[i].getURI()); } } else { System.out.println("No repository URLs are set."); } } } @Descriptor("list repository resources") public void list( @Descriptor("display all versions") @Parameter(names={ "-v", "--verbose" }, presentValue="true", absentValue="false") boolean verbose, @Descriptor("optional strings used for name matching") String[] args) throws IOException, InvalidSyntaxException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; // Create a filter that will match presentation name or symbolic name. StringBuffer sb = new StringBuffer(); if ((args == null) || (args.length == 0)) { sb.append("(|(presentationname=*)(symbolicname=*))"); } else { StringBuffer value = new StringBuffer(); for (int i = 0; i < args.length; i++) { if (i > 0) { value.append(" "); } value.append(args[i]); } sb.append("(|(presentationname=*"); sb.append(value); sb.append("*)(symbolicname=*"); sb.append(value); sb.append("*))"); } // Use filter to get matching resources. Resource[] resources = ra.discoverResources(sb.toString()); // Group the resources by symbolic name in descending version order, // but keep them in overall sorted order by presentation name. Map revisionMap = new TreeMap(new Comparator() { public int compare(Object o1, Object o2) { Resource r1 = (Resource) o1; Resource r2 = (Resource) o2; // Assume if the symbolic name is equal, then the two are equal, // since we are trying to aggregate by symbolic name. int symCompare = r1.getSymbolicName().compareTo(r2.getSymbolicName()); if (symCompare == 0) { return 0; } // Otherwise, compare the presentation name to keep them sorted // by presentation name. If the presentation names are equal, then // use the symbolic name to differentiate. int compare = (r1.getPresentationName() == null) ? -1 : (r2.getPresentationName() == null) ? 1 : r1.getPresentationName().compareToIgnoreCase( r2.getPresentationName()); if (compare == 0) { return symCompare; } return compare; } }); for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++) { Resource[] revisions = (Resource[]) revisionMap.get(resources[resIdx]); revisionMap.put(resources[resIdx], addResourceByVersion(revisions, resources[resIdx])); } // Print any matching resources. for (Iterator i = revisionMap.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry) i.next(); Resource[] revisions = (Resource[]) entry.getValue(); String name = revisions[0].getPresentationName(); name = (name == null) ? revisions[0].getSymbolicName() : name; System.out.print(name); if (verbose && revisions[0].getPresentationName() != null) { System.out.print(" [" + revisions[0].getSymbolicName() + "]"); } System.out.print(" ("); int revIdx = 0; do { if (revIdx > 0) { System.out.print(", "); } System.out.print(revisions[revIdx].getVersion()); revIdx++; } while (verbose && (revIdx < revisions.length)); if (!verbose && (revisions.length > 1)) { System.out.print(", ..."); } System.out.println(")"); } if ((resources == null) || (resources.length == 0)) { System.out.println("No matching bundles."); } } @Descriptor("retrieve resource description from repository") public void info( @Descriptor("( <bundle-name> | <symbolic-name> | <bundle-id> )[@<version>] ...") String[] args) throws IOException, InvalidSyntaxException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; for (int argIdx = 0; (args != null) && (argIdx < args.length); argIdx++) { // Find the target's bundle resource. String targetName = args[argIdx]; String targetVersion = null; int idx = args[argIdx].indexOf(VERSION_SEPARATOR); if (idx > 0) { targetName = args[argIdx].substring(0, idx); targetVersion = args[argIdx].substring(idx + 1); } Resource[] resources = searchRepository(ra, targetName, targetVersion); if ((resources == null) || (resources.length == 0)) { System.err.println("Unknown bundle and/or version: " + args[argIdx]); } else { for (int resIdx = 0; resIdx < resources.length; resIdx++) { if (resIdx > 0) { System.out.println(""); } printResource(System.out, resources[resIdx]); } } } } @Descriptor("deploy resource from repository") public void deploy( @Descriptor("start deployed bundles") @Parameter(names={ "-s", "--start" }, presentValue="true", absentValue="false") boolean start, @Descriptor("deploy required bundles only") @Parameter(names={ "-ro", "--required-only" }, presentValue="true", absentValue="false") boolean requiredOnly, @Descriptor("( <bundle-name> | <symbolic-name> | <bundle-id> )[@<version>] ...") String[] args) throws IOException, InvalidSyntaxException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; Resolver resolver = ra.resolver(); for (int argIdx = 0; (args != null) && (argIdx < args.length); argIdx++) { // Find the target's bundle resource. String targetName = args[argIdx]; String targetVersion = null; int idx = args[argIdx].indexOf(VERSION_SEPARATOR); if (idx > 0) { targetName = args[argIdx].substring(0, idx); targetVersion = args[argIdx].substring(idx + 1); } Resource resource = selectNewestVersion( searchRepository(ra, targetName, targetVersion)); if (resource != null) { resolver.add(resource); } else { System.err.println("Unknown bundle - " + args[argIdx]); } } if ((resolver.getAddedResources() != null) && (resolver.getAddedResources().length > 0)) { if (resolver.resolve()) { System.out.println("Target resource(s):"); System.out.println(getUnderlineString(19)); Resource[] resources = resolver.getAddedResources(); for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++) { System.out.println(" " + resources[resIdx].getPresentationName() + " (" + resources[resIdx].getVersion() + ")"); } resources = resolver.getRequiredResources(); if ((resources != null) && (resources.length > 0)) { System.out.println("\nRequired resource(s):"); System.out.println(getUnderlineString(21)); for (int resIdx = 0; resIdx < resources.length; resIdx++) { System.out.println(" " + resources[resIdx].getPresentationName() + " (" + resources[resIdx].getVersion() + ")"); } } if (!requiredOnly) { resources = resolver.getOptionalResources(); if ((resources != null) && (resources.length > 0)) { System.out.println("\nOptional resource(s):"); System.out.println(getUnderlineString(21)); for (int resIdx = 0; resIdx < resources.length; resIdx++) { System.out.println(" " + resources[resIdx].getPresentationName() + " (" + resources[resIdx].getVersion() + ")"); } } } try { System.out.print("\nDeploying...\n"); int options = 0; if (start) { options |= Resolver.START; } if (requiredOnly) { options |= Resolver.NO_OPTIONAL_RESOURCES; } resolver.deploy(options); System.out.println("done."); } catch (IllegalStateException ex) { System.err.println(ex); } } else { Reason[] reqs = resolver.getUnsatisfiedRequirements(); if ((reqs != null) && (reqs.length > 0)) { System.out.println("Unsatisfied requirement(s):"); System.out.println(getUnderlineString(27)); for (int reqIdx = 0; reqIdx < reqs.length; reqIdx++) { System.out.println(" " + reqs[reqIdx].getRequirement().getFilter()); System.out.println(" " + reqs[reqIdx].getResource().getPresentationName()); } } else { System.out.println("Could not resolve targets."); } } } } @Descriptor("retrieve resource source code from repository") public void source( @Descriptor("extract source code") @Parameter(names={ "-x", "--extract" }, presentValue="true", absentValue="false") boolean extract, @Descriptor("local target directory") File localDir, @Descriptor("( <bundle-name> | <symbolic-name> | <bundle-id> )[@<version>] ...") String[] args) throws IOException, InvalidSyntaxException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; for (int argIdx = 0; argIdx < args.length; argIdx++) { // Find the target's bundle resource. String targetName = args[argIdx]; String targetVersion = null; int idx = args[argIdx].indexOf(VERSION_SEPARATOR); if (idx > 0) { targetName = args[argIdx].substring(0, idx); targetVersion = args[argIdx].substring(idx + 1); } Resource resource = selectNewestVersion( searchRepository(ra, targetName, targetVersion)); if (resource == null) { System.err.println("Unknown bundle and/or version: " + args[argIdx]); } else { String srcURI = (String) resource.getProperties().get(Resource.SOURCE_URI); if (srcURI != null) { downloadSource( System.out, System.err, new URL(srcURI), localDir, extract); } else { System.err.println("Missing source URL: " + args[argIdx]); } } } } @Descriptor("retrieve resource JavaDoc from repository") public void javadoc( @Descriptor("extract documentation") @Parameter(names={"-x", "--extract" }, presentValue="true", absentValue="false") boolean extract, @Descriptor("local target directory") File localDir, @Descriptor("( <bundle-name> | <symbolic-name> | <bundle-id> )[@<version>] ...") String[] args) throws IOException, InvalidSyntaxException { Object svcObj = getRepositoryAdmin(); if (svcObj == null) { return; } RepositoryAdmin ra = (RepositoryAdmin) svcObj; for (int argIdx = 0; argIdx < args.length; argIdx++) { // Find the target's bundle resource. String targetName = args[argIdx]; String targetVersion = null; int idx = args[argIdx].indexOf(VERSION_SEPARATOR); if (idx > 0) { targetName = args[argIdx].substring(0, idx); targetVersion = args[argIdx].substring(idx + 1); } Resource resource = selectNewestVersion( searchRepository(ra, targetName, targetVersion)); if (resource == null) { System.err.println("Unknown bundle and/or version: " + args[argIdx]); } else { URL docURL = (URL) resource.getProperties().get("javadoc"); if (docURL != null) { downloadSource( System.out, System.err, docURL, localDir, extract); } else { System.err.println("Missing javadoc URL: " + args[argIdx]); } } } } private Resource[] searchRepository( RepositoryAdmin ra, String targetId, String targetVersion) throws InvalidSyntaxException { // Try to see if the targetId is a bundle ID. try { Bundle bundle = m_bc.getBundle(Long.parseLong(targetId)); if (bundle != null) { targetId = bundle.getSymbolicName(); } else { return null; } } catch (NumberFormatException ex) { // It was not a number, so ignore. } // The targetId may be a bundle name or a bundle symbolic name, // so create the appropriate LDAP query. StringBuffer sb = new StringBuffer("(|(presentationname="); sb.append(targetId); sb.append(")(symbolicname="); sb.append(targetId); sb.append("))"); if (targetVersion != null) { sb.insert(0, "(&"); sb.append("(version="); sb.append(targetVersion); sb.append("))"); } return ra.discoverResources(sb.toString()); } private Resource selectNewestVersion(Resource[] resources) { int idx = -1; Version v = null; for (int i = 0; (resources != null) && (i < resources.length); i++) { if (i == 0) { idx = 0; v = resources[i].getVersion(); } else { Version vtmp = resources[i].getVersion(); if (vtmp.compareTo(v) > 0) { idx = i; v = vtmp; } } } return (idx < 0) ? null : resources[idx]; } private void printResource(PrintStream out, Resource resource) { String presentationName = resource.getPresentationName(); if (presentationName == null) presentationName = resource.getSymbolicName(); System.out.println(getUnderlineString(presentationName.length())); out.println(presentationName); System.out.println(getUnderlineString(presentationName.length())); Map map = resource.getProperties(); for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); if (entry.getValue().getClass().isArray()) { out.println(entry.getKey() + ":"); for (int j = 0; j < Array.getLength(entry.getValue()); j++) { out.println(" " + Array.get(entry.getValue(), j)); } } else { out.println(entry.getKey() + ": " + entry.getValue()); } } Requirement[] reqs = resource.getRequirements(); if ((reqs != null) && (reqs.length > 0)) { out.println("Requires:"); for (int i = 0; i < reqs.length; i++) { out.println(" " + reqs[i].getFilter()); } } Capability[] caps = resource.getCapabilities(); if ((caps != null) && (caps.length > 0)) { out.println("Capabilities:"); for (int i = 0; i < caps.length; i++) { out.println(" " + caps[i].getPropertiesAsMap()); } } } private static Resource[] addResourceByVersion(Resource[] revisions, Resource resource) { // We want to add the resource into the array of revisions // in descending version sorted order (i.e., newest first) Resource[] sorted = null; if (revisions == null) { sorted = new Resource[] { resource }; } else { Version version = resource.getVersion(); Version middleVersion = null; int top = 0, bottom = revisions.length - 1, middle = 0; while (top <= bottom) { middle = (bottom - top) / 2 + top; middleVersion = revisions[middle].getVersion(); // Sort in reverse version order. int cmp = middleVersion.compareTo(version); if (cmp < 0) { bottom = middle - 1; } else { top = middle + 1; } } // Ignore duplicates. if ((top >= revisions.length) || (revisions[top] != resource)) { sorted = new Resource[revisions.length + 1]; System.arraycopy(revisions, 0, sorted, 0, top); System.arraycopy(revisions, top, sorted, top + 1, revisions.length - top); sorted[top] = resource; } } return sorted; } private final static StringBuffer m_sb = new StringBuffer(); public static String getUnderlineString(int len) { synchronized (m_sb) { m_sb.delete(0, m_sb.length()); for (int i = 0; i < len; i++) { m_sb.append('-'); } return m_sb.toString(); } } public static void downloadSource( PrintStream out, PrintStream err, URL srcURL, File localDir, boolean extract) { // Get the file name from the URL. String fileName = (srcURL.getFile().lastIndexOf('/') > 0) ? srcURL.getFile().substring(srcURL.getFile().lastIndexOf('/') + 1) : srcURL.getFile(); try { out.println("Connecting..."); if (!localDir.exists()) { err.println("Destination directory does not exist."); } File file = new File(localDir, fileName); OutputStream os = new FileOutputStream(file); URLConnection conn = srcURL.openConnection(); setProxyAuth(conn); int total = conn.getContentLength(); InputStream is = conn.getInputStream(); if (total > 0) { out.println("Downloading " + fileName + " ( " + total + " bytes )."); } else { out.println("Downloading " + fileName + "."); } byte[] buffer = new byte[4096]; for (int len = is.read(buffer); len > 0; len = is.read(buffer)) { os.write(buffer, 0, len); } os.close(); is.close(); if (extract) { is = new FileInputStream(file); JarInputStream jis = new JarInputStream(is); out.println("Extracting..."); unjar(jis, localDir); jis.close(); file.delete(); } } catch (Exception ex) { err.println(ex); } } public static void setProxyAuth(URLConnection conn) throws IOException { // Support for http proxy authentication String auth = System.getProperty("http.proxyAuth"); if ((auth != null) && (auth.length() > 0)) { if ("http".equals(conn.getURL().getProtocol()) || "https".equals(conn.getURL().getProtocol())) { String base64 = Base64Encoder.base64Encode(auth); conn.setRequestProperty("Proxy-Authorization", "Basic " + base64); } } } public static void unjar(JarInputStream jis, File dir) throws IOException { // Reusable buffer. byte[] buffer = new byte[4096]; // Loop through JAR entries. for (JarEntry je = jis.getNextJarEntry(); je != null; je = jis.getNextJarEntry()) { if (je.getName().startsWith("/")) { throw new IOException("JAR resource cannot contain absolute paths."); } File target = new File(dir, je.getName()); // Check to see if the JAR entry is a directory. if (je.isDirectory()) { if (!target.exists()) { if (!target.mkdirs()) { throw new IOException("Unable to create target directory: " + target); } } // Just continue since directories do not have content to copy. continue; } int lastIndex = je.getName().lastIndexOf('/'); String name = (lastIndex >= 0) ? je.getName().substring(lastIndex + 1) : je.getName(); String destination = (lastIndex >= 0) ? je.getName().substring(0, lastIndex) : ""; // JAR files use '/', so convert it to platform separator. destination = destination.replace('/', File.separatorChar); copy(jis, dir, name, destination, buffer); } } public static void copy( InputStream is, File dir, String destName, String destDir, byte[] buffer) throws IOException { if (destDir == null) { destDir = ""; } // Make sure the target directory exists and // that is actually a directory. File targetDir = new File(dir, destDir); if (!targetDir.exists()) { if (!targetDir.mkdirs()) { throw new IOException("Unable to create target directory: " + targetDir); } } else if (!targetDir.isDirectory()) { throw new IOException("Target is not a directory: " + targetDir); } BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(new File(targetDir, destName))); int count = 0; while ((count = is.read(buffer)) > 0) { bos.write(buffer, 0, count); } bos.close(); } }