Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id A78DD189FD for ; Mon, 17 Aug 2015 19:18:20 +0000 (UTC) Received: (qmail 57017 invoked by uid 500); 17 Aug 2015 19:18:14 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 56992 invoked by uid 500); 17 Aug 2015 19:18:14 -0000 Mailing-List: contact commits-help@brooklyn.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.incubator.apache.org Delivered-To: mailing list commits@brooklyn.incubator.apache.org Received: (qmail 56983 invoked by uid 99); 17 Aug 2015 19:18:14 -0000 Received: from Unknown (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 17 Aug 2015 19:18:14 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id BCA74182102 for ; Mon, 17 Aug 2015 19:18:13 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 0.775 X-Spam-Level: X-Spam-Status: No, score=0.775 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.006, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-us-east.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id bckgytSh4bVA for ; Mon, 17 Aug 2015 19:17:56 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-us-east.apache.org (ASF Mail Server at mx1-us-east.apache.org) with SMTP id 4AFB442C18 for ; Mon, 17 Aug 2015 19:17:33 +0000 (UTC) Received: (qmail 52710 invoked by uid 99); 17 Aug 2015 19:17:33 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 17 Aug 2015 19:17:33 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 17854E0375; Mon, 17 Aug 2015 19:17:33 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: hadrian@apache.org To: commits@brooklyn.incubator.apache.org Date: Mon, 17 Aug 2015 19:18:11 -0000 Message-Id: <1c9d8ee51165439e8f6811c5ff874a87@git.apache.org> In-Reply-To: <0dbe931460d14926a7da14f82aa62716@git.apache.org> References: <0dbe931460d14926a7da14f82aa62716@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [40/42] incubator-brooklyn git commit: [BROOKLYN-162] Refactor package in ./core/util http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/brooklyn/util/ResourceUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/ResourceUtils.java b/core/src/main/java/brooklyn/util/ResourceUtils.java deleted file mode 100644 index 338b223..0000000 --- a/core/src/main/java/brooklyn/util/ResourceUtils.java +++ /dev/null @@ -1,639 +0,0 @@ -/* - * 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 brooklyn.util; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.JarURLConnection; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.brooklyn.api.management.ManagementContext; -import org.apache.brooklyn.api.management.classloading.BrooklynClassLoadingContext; -import org.apache.brooklyn.core.catalog.internal.CatalogUtils; -import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker; -import org.apache.brooklyn.core.internal.BrooklynInitialization; -import org.apache.brooklyn.core.management.classloading.JavaBrooklynClassLoadingContext; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.brooklyn.location.basic.SshMachineLocation; - -import brooklyn.util.collections.MutableMap; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.http.HttpTool; -import brooklyn.util.http.HttpTool.HttpClientBuilder; -import brooklyn.util.javalang.Threads; -import brooklyn.util.net.Urls; -import brooklyn.util.os.Os; -import brooklyn.util.stream.Streams; -import brooklyn.util.text.DataUriSchemeParser; -import brooklyn.util.text.Strings; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Throwables; -import com.google.common.collect.Lists; - -public class ResourceUtils { - - private static final Logger log = LoggerFactory.getLogger(ResourceUtils.class); - private static final List> classLoaderProviders = Lists.newCopyOnWriteArrayList(); - - private BrooklynClassLoadingContext loader = null; - private String context = null; - private Object contextObject = null; - - static { BrooklynInitialization.initNetworking(); } - - /** - * Creates a {@link ResourceUtils} object with a specific class loader and context. - *

- * Use the provided {@link ClassLoader} object for class loading with the - * {@code contextObject} for context and the {@code contextMessage} string for - * error messages. - * - * @see ResourceUtils#create(Object, String) - * @see ResourceUtils#create(Object) - */ - public static final ResourceUtils create(ClassLoader loader, Object contextObject, String contextMessage) { - return new ResourceUtils(loader, contextObject, contextMessage); - } - - /** - * Creates a {@link ResourceUtils} object with a specific class loader and context. - *

- * Use the provided {@link BrooklynClassLoadingContext} object for class loading with the - * {@code contextObject} for context and the {@code contextMessage} string for - * error messages. - * - * @see ResourceUtils#create(Object, String) - * @see ResourceUtils#create(Object) - */ - public static final ResourceUtils create(BrooklynClassLoadingContext loader, Object contextObject, String contextMessage) { - return new ResourceUtils(loader, contextObject, contextMessage); - } - - /** - * Creates a {@link ResourceUtils} object with the given context. - *

- * Uses the {@link ClassLoader} of the given {@code contextObject} for class - * loading and the {@code contextMessage} string for error messages. - * - * @see ResourceUtils#create(ClassLoader, Object, String) - * @see ResourceUtils#create(Object) - */ - public static final ResourceUtils create(Object contextObject, String contextMessage) { - return new ResourceUtils(contextObject, contextMessage); - } - - /** - * Creates a {@link ResourceUtils} object with the given context. - *

- * Uses the {@link ClassLoader} of the given {@code contextObject} for class - * loading and its {@link Object#toString()} (preceded by the word 'for') as - * the string used in error messages. - * - * @see ResourceUtils#create(ClassLoader, Object, String) - * @see ResourceUtils#create(Object) - */ - public static final ResourceUtils create(Object contextObject) { - return new ResourceUtils(contextObject); - } - - /** - * Creates a {@link ResourceUtils} object with itself as the context. - * - * @see ResourceUtils#create(Object) - */ - public static final ResourceUtils create() { - return new ResourceUtils(null); - } - - public ResourceUtils(ClassLoader loader, Object contextObject, String contextMessage) { - this(getClassLoadingContextInternal(loader, contextObject), contextObject, contextMessage); - } - - public ResourceUtils(BrooklynClassLoadingContext loader, Object contextObject, String contextMessage) { - this.loader = loader; - this.contextObject = contextObject; - this.context = contextMessage; - } - - public ResourceUtils(Object contextObject, String contextMessage) { - this(contextObject==null ? null : getClassLoadingContextInternal(null, contextObject), contextObject, contextMessage); - } - - public ResourceUtils(Object contextObject) { - this(contextObject, Strings.toString(contextObject)); - } - - /** used to register custom mechanisms for getting classloaders given an object */ - public static void addClassLoaderProvider(Function provider) { - classLoaderProviders.add(provider); - } - - // TODO rework this class so it accepts but does not require a BCLC ? - @SuppressWarnings("deprecation") - protected static BrooklynClassLoadingContext getClassLoadingContextInternal(ClassLoader loader, Object contextObject) { - if (contextObject instanceof BrooklynClassLoadingContext) - return (BrooklynClassLoadingContext) contextObject; - - for (Function provider: classLoaderProviders) { - BrooklynClassLoadingContext result = provider.apply(contextObject); - if (result!=null) return result; - } - - BrooklynClassLoadingContext bl = BrooklynLoaderTracker.getLoader(); - ManagementContext mgmt = (bl!=null ? bl.getManagementContext() : null); - - ClassLoader cl = loader; - if (cl==null) cl = contextObject instanceof Class ? ((Class)contextObject).getClassLoader() : - contextObject instanceof ClassLoader ? ((ClassLoader)contextObject) : - contextObject.getClass().getClassLoader(); - - return JavaBrooklynClassLoadingContext.create(mgmt, cl); - } - - /** This should not be exposed as it risks it leaking into places where it would be serialized. - * Better for callers use {@link CatalogUtils#getClassLoadingContext(org.apache.brooklyn.api.entity.Entity)} or similar. }. - */ - private BrooklynClassLoadingContext getLoader() { - return (loader!=null ? loader : getClassLoadingContextInternal(null, contextObject!=null ? contextObject : this)); - } - - /** - * @return all resources in Brooklyn's {@link BrooklynClassLoadingContext} with the given name. - */ - public Iterable getResources(String name) { - return getLoader().getResources(name); - } - - /** - * Takes a string which is treated as a URL (with some extended "schemes" also expected), - * or as a path to something either on the classpath (absolute only) or the local filesystem (relative or absolute, depending on leading slash) - *

- * URLs can be of the form classpath://com/acme/Foo.properties - * as well as file:///home/... and http://acme.com/.... - *

- * Throws exception if not found, using the context parameter passed into the constructor. - *

- * TODO may want OSGi, or typed object; should consider pax url - * - * @return a stream, or throws exception (never returns null) - */ - public InputStream getResourceFromUrl(String url) { - try { - if (url==null) throw new NullPointerException("Cannot read from null"); - if (url=="") throw new NullPointerException("Cannot read from empty string"); - String orig = url; - String protocol = Urls.getProtocol(url); - if (protocol!=null) { - if ("classpath".equals(protocol)) { - try { - return getResourceViaClasspath(url); - } catch (IOException e) { - //catch the above because both orig and modified url may be interesting - throw new IOException("Error accessing "+orig+": "+e, e); - } - } - if ("sftp".equals(protocol)) { - try { - return getResourceViaSftp(url); - } catch (IOException e) { - throw new IOException("Error accessing "+orig+": "+e, e); - } - } - - if ("file".equals(protocol)) - url = tidyFileUrl(url); - - if ("data".equals(protocol)) { - return new DataUriSchemeParser(url).lax().parse().getDataAsInputStream(); - } - - if ("http".equals(protocol) || "https".equals(protocol)) { - return getResourceViaHttp(url); - } - - return new URL(url).openStream(); - } - - try { - //try as classpath reference, then as file - try { - URL u = getLoader().getResource(url); - if (u!=null) return u.openStream(); - } catch (IllegalArgumentException e) { - //Felix installs an additional URL to the system classloader - //which throws an IllegalArgumentException when passed a - //windows path. See ExtensionManager.java static initializer. - - //ignore, not a classpath resource - } - if (url.startsWith("/")) { - //some getResource calls fail if argument starts with / - String urlNoSlash = url; - while (urlNoSlash.startsWith("/")) urlNoSlash = urlNoSlash.substring(1); - URL u = getLoader().getResource(urlNoSlash); - if (u!=null) return u.openStream(); -// //Class.getResource can require a / (else it attempts to be relative) but Class.getClassLoader doesn't -// u = getLoader().getResource("/"+urlNoSlash); -// if (u!=null) return u.openStream(); - } - File f; - // but first, if it starts with tilde, treat specially - if (url.startsWith("~/")) { - f = new File(Os.home(), url.substring(2)); - } else if (url.startsWith("~\\")) { - f = new File(Os.home(), url.substring(2)); - } else { - f = new File(url); - } - if (f.exists()) return new FileInputStream(f); - } catch (IOException e) { - //catch the above because both u and modified url will be interesting - throw new IOException("Error accessing "+orig+": "+e, e); - } - throw new IOException("'"+orig+"' not found on classpath or filesystem"); - } catch (Exception e) { - if (context!=null) { - throw new RuntimeException("Error getting resource '"+url+"' for "+context+": "+e, e); - } else { - throw Exceptions.propagate(e); - } - } - } - - private final static Pattern pattern = Pattern.compile("^file:/*~/+(.*)$"); - - public static URL tidy(URL url) { - // File class has helpful methods for URIs but not URLs. So we convert. - URI in; - try { - in = url.toURI(); - } catch (URISyntaxException e) { - throw Exceptions.propagate(e); - } - URI out; - - Matcher matcher = pattern.matcher(in.toString()); - if (matcher.matches()) { - // home-relative - File home = new File(Os.home()); - File file = new File(home, matcher.group(1)); - out = file.toURI(); - } else if (in.getScheme().equals("file:")) { - // some other file, so canonicalize - File file = new File(in); - out = file.toURI(); - } else { - // some other scheme, so no-op - out = in; - } - - URL urlOut; - try { - urlOut = out.toURL(); - } catch (MalformedURLException e) { - throw Exceptions.propagate(e); - } - if (!urlOut.equals(url) && log.isDebugEnabled()) { - log.debug("quietly changing " + url + " to " + urlOut); - } - return urlOut; - } - - public static String tidyFileUrl(String url) { - try { - return tidy(new URL(url)).toString(); - } catch (MalformedURLException e) { - throw Exceptions.propagate(e); - } - } - - /** @deprecated since 0.7.0; use method {@link Os#mergePaths(String...)} */ @Deprecated - public static String mergeFilePaths(String... items) { - return Os.mergePaths(items); - } - - /** @deprecated since 0.7.0; use method {@link Os#tidyPath(String)} */ @Deprecated - public static String tidyFilePath(String path) { - return Os.tidyPath(path); - } - - /** @deprecated since 0.7.0; use method {@link Urls#getProtocol(String)} */ @Deprecated - public static String getProtocol(String url) { - return Urls.getProtocol(url); - } - - private InputStream getResourceViaClasspath(String url) throws IOException { - assert url.startsWith("classpath:"); - String subUrl = url.substring("classpath:".length()); - while (subUrl.startsWith("/")) subUrl = subUrl.substring(1); - URL u = getLoader().getResource(subUrl); - if (u!=null) return u.openStream(); - else throw new IOException(subUrl+" not found on classpath"); - } - - private InputStream getResourceViaSftp(String url) throws IOException { - assert url.startsWith("sftp://"); - String subUrl = url.substring("sftp://".length()); - String user; - String address; - String path; - int atIndex = subUrl.indexOf("@"); - int colonIndex = subUrl.indexOf(":", (atIndex > 0 ? atIndex : 0)); - if (colonIndex <= 0 || colonIndex <= atIndex) { - throw new IllegalArgumentException("Invalid sftp url ("+url+"); IP or hostname must be specified, such as sftp://localhost:/path/to/file"); - } - if (subUrl.length() <= (colonIndex+1)) { - throw new IllegalArgumentException("Invalid sftp url ("+url+"); must specify path of remote file, such as sftp://localhost:/path/to/file"); - } - if (atIndex >= 0) { - user = subUrl.substring(0, atIndex); - } else { - user = null; - } - address = subUrl.substring(atIndex + 1, colonIndex); - path = subUrl.substring(colonIndex+1); - - // TODO messy way to get an SCP session - SshMachineLocation machine = new SshMachineLocation(MutableMap.builder() - .putIfNotNull("user", user) - .put("address", InetAddress.getByName(address)) - .build()); - try { - final File tempFile = Os.newTempFile("brooklyn-sftp", "tmp"); - tempFile.setReadable(true, true); - machine.copyFrom(path, tempFile.getAbsolutePath()); - return new FileInputStream(tempFile) { - @Override - public void close() throws IOException { - super.close(); - tempFile.delete(); - } - }; - } finally { - Streams.closeQuietly(machine); - } - } - - //For HTTP(S) targets use HttpClient so - //we can do authentication - private InputStream getResourceViaHttp(String resource) throws IOException { - URI uri = URI.create(resource); - HttpClientBuilder builder = HttpTool.httpClientBuilder() - .laxRedirect(true) - .uri(uri); - Credentials credentials = getUrlCredentials(uri.getRawUserInfo()); - if (credentials != null) { - builder.credentials(credentials); - } - HttpClient client = builder.build(); - HttpResponse result = client.execute(new HttpGet(resource)); - int statusCode = result.getStatusLine().getStatusCode(); - if (HttpTool.isStatusCodeHealthy(statusCode)) { - HttpEntity entity = result.getEntity(); - if (entity != null) { - return entity.getContent(); - } else { - return new ByteArrayInputStream(new byte[0]); - } - } else { - EntityUtils.consume(result.getEntity()); - throw new IllegalStateException("Invalid response invoking " + resource + ": response code " + statusCode); - } - } - - private Credentials getUrlCredentials(String userInfo) { - if (userInfo != null) { - String[] arr = userInfo.split(":"); - String username; - String password = null; - if (arr.length == 1) { - username = urlDecode(arr[0]); - } else if (arr.length == 2) { - username = urlDecode(arr[0]); - password = urlDecode(arr[1]); - } else { - return null; - } - return new UsernamePasswordCredentials(username, password); - } else { - return null; - } - } - - private String urlDecode(String str) { - try { - return URLDecoder.decode(str, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw Exceptions.propagate(e); - } - } - - /** takes {@link #getResourceFromUrl(String)} and reads fully, into a string */ - public String getResourceAsString(String url) { - try { - return readFullyString(getResourceFromUrl(url)); - } catch (Exception e) { - log.debug("ResourceUtils got error reading "+url+(context==null?"":" "+context)+" (rethrowing): "+e); - throw Throwables.propagate(e); - } - } - - /** allows failing-fast if URL cannot be read */ - public String checkUrlExists(String url) { - return checkUrlExists(url, null); - } - - public String checkUrlExists(String url, String message) { - if (url==null) throw new NullPointerException("URL "+(message!=null ? message+" " : "")+"must not be null"); - InputStream s; - try { - s = getResourceFromUrl(url); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - throw new IllegalArgumentException("Unable to access URL "+(message!=null ? message : "")+": "+url, e); - } - Streams.closeQuietly(s); - return url; - } - - /** tests whether the url exists, returning true or false */ - public boolean doesUrlExist(String url) { - InputStream s = null; - try { - s = getResourceFromUrl(url); - return true; - } catch (Exception e) { - return false; - } finally { - Streams.closeQuietly(s); - } - } - - /** returns the first available URL */ - public Optional firstAvailableUrl(String ...urls) { - for (String url: urls) { - if (doesUrlExist(url)) return Optional.of(url); - } - return Optional.absent(); - } - - /** returns the base directory or JAR from which the context is class-loaded, if possible; - * throws exception if not found */ - public String getClassLoaderDir() { - if (contextObject==null) throw new IllegalArgumentException("No suitable context ("+context+") to auto-detect classloader dir"); - Class cc = contextObject instanceof Class ? (Class)contextObject : contextObject.getClass(); - return getClassLoaderDir(cc.getCanonicalName().replace('.', '/')+".class"); - } - - public String getClassLoaderDir(String resourceInThatDir) { - resourceInThatDir = Strings.removeFromStart(resourceInThatDir, "/"); - URL resourceUrl = getLoader().getResource(resourceInThatDir); - if (resourceUrl==null) throw new NoSuchElementException("Resource ("+resourceInThatDir+") not found"); - - URL containerUrl = getContainerUrl(resourceUrl, resourceInThatDir); - - if (!"file".equals(containerUrl.getProtocol())) throw new IllegalStateException("Resource ("+resourceInThatDir+") not on file system (at "+containerUrl+")"); - - //convert from file: URL to File - File file; - try { - file = new File(containerUrl.toURI()); - } catch (URISyntaxException e) { - throw new IllegalStateException("Resource ("+resourceInThatDir+") found at invalid URI (" + containerUrl + ")", e); - } - - if (!file.exists()) throw new IllegalStateException("Context class url substring ("+containerUrl+") not found on filesystem"); - return file.getPath(); - - } - - public static URL getContainerUrl(URL url, String resourceInThatDir) { - //Switching from manual parsing of jar: and file: URLs to java provided functionality. - //The old code was breaking on any Windows path and instead of fixing it, using - //the provided Java APIs seemed like the better option since they are already tested - //on multiple platforms. - boolean isJar = "jar".equals(url.getProtocol()); - if(isJar) { - try { - //let java handle the parsing of jar URL, no network connection is established. - //Strips the jar protocol: - // jar:file:/! - // becomes - // file:/ - JarURLConnection connection = (JarURLConnection) url.openConnection(); - url = connection.getJarFileURL(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } else { - //Remove the trailing resouceInThatDir path from the URL, thus getting the parent folder. - String path = url.toString(); - int i = path.indexOf(resourceInThatDir); - if (i==-1) throw new IllegalStateException("Resource path ("+resourceInThatDir+") not in url substring ("+url+")"); - String parent = path.substring(0, i); - try { - url = new URL(parent); - } catch (MalformedURLException e) { - throw new IllegalStateException("Resource ("+resourceInThatDir+") found at invalid URL parent (" + parent + ")", e); - } - } - return url; - } - - /** @deprecated since 0.7.0 use {@link Streams#readFullyString(InputStream) */ @Deprecated - public static String readFullyString(InputStream is) throws IOException { - return Streams.readFullyString(is); - } - - /** @deprecated since 0.7.0 use {@link Streams#readFully(InputStream) */ @Deprecated - public static byte[] readFullyBytes(InputStream is) throws IOException { - return Streams.readFully(is); - } - - /** @deprecated since 0.7.0 use {@link Streams#copy(InputStream, OutputStream)} */ @Deprecated - public static void copy(InputStream input, OutputStream output) throws IOException { - Streams.copy(input, output); - } - - /** @deprecated since 0.7.0; use same method in {@link Os} */ @Deprecated - public static File mkdirs(File dir) { - return Os.mkdirs(dir); - } - - /** @deprecated since 0.7.0; use same method in {@link Os} */ @Deprecated - public static File writeToTempFile(InputStream is, String prefix, String suffix) { - return Os.writeToTempFile(is, prefix, suffix); - } - - /** @deprecated since 0.7.0; use same method in {@link Os} */ @Deprecated - public static File writeToTempFile(InputStream is, File tempDir, String prefix, String suffix) { - return Os.writeToTempFile(is, tempDir, prefix, suffix); - } - - /** @deprecated since 0.7.0; use method {@link Os#writePropertiesToTempFile(Properties, String, String)} */ @Deprecated - public static File writeToTempFile(Properties props, String prefix, String suffix) { - return Os.writePropertiesToTempFile(props, prefix, suffix); - } - - /** @deprecated since 0.7.0; use method {@link Os#writePropertiesToTempFile(Properties, File, String, String)} */ @Deprecated - public static File writeToTempFile(Properties props, File tempDir, String prefix, String suffix) { - return Os.writePropertiesToTempFile(props, tempDir, prefix, suffix); - } - - /** @deprecated since 0.7.0; use method {@link Threads#addShutdownHook(Runnable)} */ @Deprecated - public static Thread addShutdownHook(final Runnable task) { - return Threads.addShutdownHook(task); - } - /** @deprecated since 0.7.0; use method {@link Threads#removeShutdownHook(Thread)} */ @Deprecated - public static boolean removeShutdownHook(Thread hook) { - return Threads.removeShutdownHook(hook); - } - - /** returns the items with exactly one "/" between items (whether or not the individual items start or end with /), - * except where character before the / is a : (url syntax) in which case it will permit multiple (will not remove any) - * @deprecated since 0.7.0 use either {@link Os#mergePathsUnix(String...)} {@link Urls#mergePaths(String...) */ @Deprecated - public static String mergePaths(String ...items) { - return Urls.mergePaths(items); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/brooklyn/util/config/ConfigBag.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/config/ConfigBag.java b/core/src/main/java/brooklyn/util/config/ConfigBag.java deleted file mode 100644 index 2f17748..0000000 --- a/core/src/main/java/brooklyn/util/config/ConfigBag.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * 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 brooklyn.util.config; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.util.ConcurrentModificationException; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.annotation.Nonnull; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.config.ConfigKey; -import brooklyn.config.ConfigKey.HasConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.flags.TypeCoercions; -import brooklyn.util.guava.Maybe; -import brooklyn.util.javalang.JavaClassNames; - -import com.google.common.annotations.Beta; -import com.google.common.base.Objects; -import com.google.common.collect.Sets; - -/** - * Stores config in such a way that usage can be tracked. - * Either {@link ConfigKey} or {@link String} keys can be inserted; - * they will be stored internally as strings. - * It is recommended to use {@link ConfigKey} instances to access, - * although in some cases (such as setting fields from flags, or copying a map) - * it may be necessary to mark things as used, or put, when only a string key is available. - *

- * This bag is order-preserving and thread-safe except where otherwise indicated, - * currently by synching on this instance (but that behaviour may change). - *

- * @author alex - */ -public class ConfigBag { - - private static final Logger log = LoggerFactory.getLogger(ConfigBag.class); - - /** an immutable, empty ConfigBag */ - public static final ConfigBag EMPTY = new ConfigBag().setDescription("immutable empty config bag").seal(); - - protected String description; - - private Map config; - private final Map unusedConfig; - private final boolean live; - private boolean sealed = false; - - /** creates a new ConfigBag instance, empty and ready for population */ - public static ConfigBag newInstance() { - return new ConfigBag(); - } - - /** - * Creates an instance that is backed by a "live map" (e.g. storage in a datagrid). - * The order-preserving nature of this class is only guaranteed if the - * provided storage has those properties. External modifications to the store can cause - * {@link ConcurrentModificationException} to be thrown, here or elsewhere. - */ - public static ConfigBag newLiveInstance(Map storage) { - return new ConfigBag(checkNotNull(storage, "storage map must be specified")); - } - - public static ConfigBag newInstance(Map config) { - ConfigBag result = new ConfigBag(); - result.putAll(config); - return result; - } - - /** creates a new ConfigBag instance which includes all of the supplied ConfigBag's values, - * but which tracks usage separately (already used values are marked as such, - * but uses in the original set will not be marked here, and vice versa) */ - public static ConfigBag newInstanceCopying(final ConfigBag configBag) { - return new ConfigBag().copy(configBag).setDescription(configBag.getDescription()); - } - - /** creates a new ConfigBag instance which includes all of the supplied ConfigBag's values, - * plus an additional set of <ConfigKey,Object> or <String,Object> pairs - *

- * values from the original set which are used here will be marked as used in the original set - * (note: this applies even for values which are overridden and the overridden value is used); - * however subsequent uses in the original set will not be marked here - */ - @Beta - public static ConfigBag newInstanceExtending(final ConfigBag parentBag) { - return new ConfigBagExtendingParent(parentBag); - } - - /** @see #newInstanceExtending(ConfigBag) */ - private static class ConfigBagExtendingParent extends ConfigBag { - ConfigBag parentBag; - private ConfigBagExtendingParent(ConfigBag parentBag) { - this.parentBag = parentBag; - copy(parentBag); - } - @Override - public void markUsed(String key) { - super.markUsed(key); - if (parentBag!=null) - parentBag.markUsed(key); - } - } - - /** As {@link #newInstanceExtending(ConfigBag)} but also putting the supplied values. */ - @Beta - public static ConfigBag newInstanceExtending(final ConfigBag configBag, Map optionalAdditionalValues) { - return newInstanceExtending(configBag).putAll(optionalAdditionalValues); - } - - /** @deprecated since 0.7.0, not used; kept only for rebind compatibility where the inner class is used - * (now replaced by a static class above) */ - @Beta @Deprecated - public static ConfigBag newInstanceWithInnerClass(final ConfigBag configBag, Map optionalAdditionalValues) { - return new ConfigBag() { - @Override - public void markUsed(String key) { - super.markUsed(key); - configBag.markUsed(key); - } - }.copy(configBag).putAll(optionalAdditionalValues); - } - - public ConfigBag() { - config = new LinkedHashMap(); - unusedConfig = new LinkedHashMap(); - live = false; - } - - private ConfigBag(Map storage) { - this.config = storage; - unusedConfig = new LinkedHashMap(); - live = true; - } - - public ConfigBag setDescription(String description) { - if (sealed) - throw new IllegalStateException("Cannot set description to '"+description+"': this config bag has been sealed and is now immutable."); - this.description = description; - return this; - } - - /** optional description used to provide context for operations */ - public String getDescription() { - return description; - } - - /** current values for all entries - * @return non-modifiable map of strings to object */ - public synchronized Map getAllConfig() { - return MutableMap.copyOf(config).asUnmodifiable(); - } - - /** current values for all entries in a map where the keys are converted to {@link ConfigKey} instances */ - public synchronized Map, ?> getAllConfigAsConfigKeyMap() { - Map,Object> result = MutableMap.of(); - for (Map.Entry entry: config.entrySet()) { - result.put(ConfigKeys.newConfigKey(Object.class, entry.getKey()), entry.getValue()); - } - return result; - } - - /** Returns the internal map containing the current values for all entries; - * for use where the caller wants to modify this directly and knows it is safe to do so - *

- * Accesses to the returned map must be synchronized on this bag if the - * thread-safe behaviour is required. */ - public Map getAllConfigMutable() { - if (live) { - // TODO sealed no longer works as before, because `config` is the backing storage map. - // Therefore returning it is dangerous! Even if we were to replace our field with an immutable copy, - // the underlying datagrid's map would still be modifiable. We need a way to switch the returned - // value's behaviour to sealable (i.e. wrapping the returned map). - return (sealed) ? MutableMap.copyOf(config).asUnmodifiable() : config; - } else { - return config; - } - } - - /** current values for all entries which have not yet been used - * @return non-modifiable map of strings to object */ - public synchronized Map getUnusedConfig() { - return MutableMap.copyOf(unusedConfig).asUnmodifiable(); - } - - /** Returns the internal map containing the current values for all entries which have not yet been used; - * for use where the caller wants to modify this directly and knows it is safe to do so - *

- * Accesses to the returned map must be synchronized on this bag if the - * thread-safe behaviour is required. */ - public Map getUnusedConfigMutable() { - return unusedConfig; - } - - public ConfigBag putAll(Map addlConfig) { - if (addlConfig==null) return this; - for (Map.Entry e: addlConfig.entrySet()) { - putAsStringKey(e.getKey(), e.getValue()); - } - return this; - } - - public ConfigBag putAll(ConfigBag addlConfig) { - return putAll(addlConfig.getAllConfig()); - } - - public ConfigBag putIfAbsent(ConfigKey key, T value) { - return putIfAbsent(MutableMap.of(key, value)); - } - - public ConfigBag putAsStringKeyIfAbsent(Object key, Object value) { - return putIfAbsent(MutableMap.of(key, value)); - } - - public synchronized ConfigBag putIfAbsent(Map propertiesToSet) { - if (propertiesToSet==null) - return this; - for (Map.Entry entry: propertiesToSet.entrySet()) { - Object key = entry.getKey(); - if (key instanceof HasConfigKey) - key = ((HasConfigKey)key).getConfigKey(); - if (key instanceof ConfigKey) { - if (!containsKey((ConfigKey)key)) - putAsStringKey(key, entry.getValue()); - } else if (key instanceof String) { - if (!containsKey((String)key)) - putAsStringKey(key, entry.getValue()); - } else { - logInvalidKey(key); - } - } - return this; - } - - public ConfigBag putIfAbsent(ConfigBag addlConfig) { - return putIfAbsent(addlConfig.getAllConfig()); - } - - - @SuppressWarnings("unchecked") - public T put(ConfigKey key, T value) { - return (T) putStringKey(key.getName(), value); - } - - public ConfigBag putIfNotNull(ConfigKey key, T value) { - if (value!=null) put(key, value); - return this; - } - - public ConfigBag putIfAbsentAndNotNull(ConfigKey key, T value) { - if (value!=null) putIfAbsent(key, value); - return this; - } - - /** as {@link #put(ConfigKey, Object)} but returning this ConfigBag for fluent-style coding */ - public ConfigBag configure(ConfigKey key, T value) { - putStringKey(key.getName(), value); - return this; - } - - public ConfigBag configureStringKey(String key, T value) { - putStringKey(key, value); - return this; - } - - protected synchronized void putAsStringKey(Object key, Object value) { - if (key instanceof HasConfigKey) key = ((HasConfigKey)key).getConfigKey(); - if (key instanceof ConfigKey) key = ((ConfigKey)key).getName(); - if (key instanceof String) { - putStringKey((String)key, value); - } else { - logInvalidKey(key); - } - } - - protected void logInvalidKey(Object key) { - String message = (key == null ? "Invalid key 'null'" : "Invalid key type "+key.getClass().getCanonicalName()+" ("+key+")") + - " being used for configuration, ignoring"; - log.debug(message, new Throwable("Source of "+message)); - log.warn(message); - } - - /** recommended to use {@link #put(ConfigKey, Object)} but there are times - * (e.g. when copying a map) where we want to put a string key directly - */ - public synchronized Object putStringKey(String key, Object value) { - if (sealed) - throw new IllegalStateException("Cannot insert "+key+"="+value+": this config bag has been sealed and is now immutable."); - boolean isNew = !config.containsKey(key); - boolean isUsed = !isNew && !unusedConfig.containsKey(key); - Object old = config.put(key, value); - if (!isUsed) - unusedConfig.put(key, value); - //if (!isNew && !isUsed) log.debug("updating config value which has already been used"); - return old; - } - public Object putStringKeyIfHasValue(String key, Maybe value) { - if (value.isPresent()) - return putStringKey(key, value.get()); - return null; - } - public Object putStringKeyIfNotNull(String key, Object value) { - if (value!=null) - return putStringKey(key, value); - return null; - } - - public boolean containsKey(HasConfigKey key) { - return containsKey(key.getConfigKey()); - } - - public boolean containsKey(ConfigKey key) { - return containsKey(key.getName()); - } - - public synchronized boolean containsKey(String key) { - return config.containsKey(key); - } - - /** returns the value of this config key, falling back to its default (use containsKey to see whether it was contained); - * also marks it as having been used (use peek to prevent marking as used) - */ - public T get(ConfigKey key) { - return get(key, true); - } - - /** gets a value from a string-valued key or null; ConfigKey is preferred, but this is useful in some contexts (e.g. setting from flags) */ - public Object getStringKey(String key) { - return getStringKeyMaybe(key).orNull(); - } - /** gets a {@link Maybe}-wrapped value from a string-valued key; ConfigKey is preferred, but this is useful in some contexts (e.g. setting from flags) */ - public @Nonnull Maybe getStringKeyMaybe(String key) { - return getStringKeyMaybe(key, true); - } - - /** gets a {@link Maybe}-wrapped value from a key, inferring the type of that key (e.g. {@link ConfigKey} or {@link String}) */ - @Beta - public Maybe getObjKeyMaybe(Object key) { - if (key instanceof HasConfigKey) key = ((HasConfigKey)key).getConfigKey(); - if (key instanceof ConfigKey) key = ((ConfigKey)key).getName(); - if (key instanceof String) { - return getStringKeyMaybe((String)key, true); - } else { - logInvalidKey(key); - return Maybe.absent(); - } - } - - /** like get, but without marking it as used */ - public T peek(ConfigKey key) { - return get(key, false); - } - - /** returns the first key in the list for which a value is explicitly set, then defaulting to defaulting value of preferred key */ - public synchronized T getFirst(ConfigKey preferredKey, ConfigKey ...otherCurrentKeysInOrderOfPreference) { - if (containsKey(preferredKey)) - return get(preferredKey); - for (ConfigKey key: otherCurrentKeysInOrderOfPreference) { - if (containsKey(key)) - return get(key); - } - return get(preferredKey); - } - - /** convenience for @see #getWithDeprecation(ConfigKey[], ConfigKey...) */ - public Object getWithDeprecation(ConfigKey key, ConfigKey ...deprecatedKeys) { - return getWithDeprecation(new ConfigKey[] { key }, deprecatedKeys); - } - - /** returns the value for the first key in the list for which a value is set, - * warning if any of the deprecated keys have a value which is different to that set on the first set current key - * (including warning if a deprecated key has a value but no current key does) */ - public synchronized Object getWithDeprecation(ConfigKey[] currentKeysInOrderOfPreference, ConfigKey ...deprecatedKeys) { - // Get preferred key (or null) - ConfigKey preferredKeyProvidingValue = null; - Object result = null; - boolean found = false; - for (ConfigKey key: currentKeysInOrderOfPreference) { - if (containsKey(key)) { - preferredKeyProvidingValue = key; - result = get(preferredKeyProvidingValue); - found = true; - break; - } - } - - // Check if any deprecated keys are set - ConfigKey deprecatedKeyProvidingValue = null; - Object deprecatedResult = null; - boolean foundDeprecated = false; - for (ConfigKey deprecatedKey: deprecatedKeys) { - Object x = null; - boolean foundX = false; - if (containsKey(deprecatedKey)) { - x = get(deprecatedKey); - foundX = true; - } - if (foundX) { - if (found) { - if (!Objects.equal(result, x)) { - log.warn("Conflicting value from deprecated key " +deprecatedKey+", value "+x+ - "; using preferred key "+preferredKeyProvidingValue+" value "+result); - } else { - log.info("Deprecated key " +deprecatedKey+" ignored; has same value as preferred key "+preferredKeyProvidingValue+" ("+result+")"); - } - } else if (foundDeprecated) { - if (!Objects.equal(result, x)) { - log.warn("Conflicting values from deprecated keys: using " +deprecatedKeyProvidingValue+" instead of "+deprecatedKey+ - " (value "+deprecatedResult+" instead of "+x+")"); - } else { - log.info("Deprecated key " +deprecatedKey+" ignored; has same value as other deprecated key "+preferredKeyProvidingValue+" ("+deprecatedResult+")"); - } - } else { - // new value, from deprecated key - log.warn("Deprecated key " +deprecatedKey+" detected (supplying value "+x+"), "+ - "; recommend changing to preferred key '"+currentKeysInOrderOfPreference[0]+"'; this will not be supported in future versions"); - deprecatedResult = x; - deprecatedKeyProvidingValue = deprecatedKey; - foundDeprecated = true; - } - } - } - - if (found) { - return result; - } else if (foundDeprecated) { - return deprecatedResult; - } else { - return currentKeysInOrderOfPreference[0].getDefaultValue(); - } - } - - protected T get(ConfigKey key, boolean markUsed) { - // TODO for now, no evaluation -- maps / closure content / other smart (self-extracting) keys are NOT supported - // (need a clean way to inject that behaviour, as well as desired TypeCoercions) - // this method, and the coercion, is not synchronized, nor does it need to be, because the "get" is synchronized. - return coerceFirstNonNullKeyValue(key, getStringKey(key.getName(), markUsed)); - } - - /** returns the first non-null value to be the type indicated by the key, or the keys default value if no non-null values are supplied */ - public static T coerceFirstNonNullKeyValue(ConfigKey key, Object ...values) { - for (Object o: values) - if (o!=null) return TypeCoercions.coerce(o, key.getTypeToken()); - return TypeCoercions.coerce(key.getDefaultValue(), key.getTypeToken()); - } - - protected Object getStringKey(String key, boolean markUsed) { - return getStringKeyMaybe(key, markUsed).orNull(); - } - protected synchronized Maybe getStringKeyMaybe(String key, boolean markUsed) { - if (config.containsKey(key)) { - if (markUsed) markUsed(key); - return Maybe.of(config.get(key)); - } - return Maybe.absent(); - } - - /** indicates that a string key in the config map has been accessed */ - public synchronized void markUsed(String key) { - unusedConfig.remove(key); - } - - public synchronized void clear() { - if (sealed) - throw new IllegalStateException("Cannot clear this config bag has been sealed and is now immutable."); - config.clear(); - unusedConfig.clear(); - } - - public ConfigBag removeAll(ConfigKey ...keys) { - for (ConfigKey key: keys) remove(key); - return this; - } - - public synchronized void remove(ConfigKey key) { - remove(key.getName()); - } - - public ConfigBag removeAll(Iterable keys) { - for (String key: keys) remove(key); - return this; - } - - public synchronized void remove(String key) { - if (sealed) - throw new IllegalStateException("Cannot remove "+key+": this config bag has been sealed and is now immutable."); - config.remove(key); - unusedConfig.remove(key); - } - - public ConfigBag copy(ConfigBag other) { - // ensure locks are taken in a canonical order to prevent deadlock - if (other==null) { - synchronized (this) { - return copyWhileSynched(other); - } - } - if (System.identityHashCode(other) < System.identityHashCode(this)) { - synchronized (other) { - synchronized (this) { - return copyWhileSynched(other); - } - } - } else { - synchronized (this) { - synchronized (other) { - return copyWhileSynched(other); - } - } - } - } - - protected ConfigBag copyWhileSynched(ConfigBag other) { - if (sealed) - throw new IllegalStateException("Cannot copy "+other+" to "+this+": this config bag has been sealed and is now immutable."); - putAll(other.getAllConfig()); - markAll(Sets.difference(other.getAllConfig().keySet(), other.getUnusedConfig().keySet())); - setDescription(other.getDescription()); - return this; - } - - public synchronized int size() { - return config.size(); - } - - public synchronized boolean isEmpty() { - return config.isEmpty(); - } - - public ConfigBag markAll(Iterable usedFlags) { - for (String flag: usedFlags) - markUsed(flag); - return this; - } - - public synchronized boolean isUnused(ConfigKey key) { - return unusedConfig.containsKey(key.getName()); - } - - /** makes this config bag immutable; any attempts to change subsequently - * (apart from marking fields as used) will throw an exception - *

- * copies will be unsealed however - *

- * returns this for convenience (fluent usage) */ - public ConfigBag seal() { - sealed = true; - if (live) { - // TODO How to ensure sealed?! - } else { - config = getAllConfig(); - } - return this; - } - - // TODO why have both this and mutable - /** @see #getAllConfigMutable() */ - public Map getAllConfigRaw() { - return getAllConfigMutable(); - } - - @Override - public String toString() { - return JavaClassNames.simpleClassName(this)+"["+getAllConfigRaw()+"]"; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/brooklyn/util/crypto/FluentKeySigner.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/crypto/FluentKeySigner.java b/core/src/main/java/brooklyn/util/crypto/FluentKeySigner.java deleted file mode 100644 index a1bc125..0000000 --- a/core/src/main/java/brooklyn/util/crypto/FluentKeySigner.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 brooklyn.util.crypto; - -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.CertificateParsingException; -import java.security.cert.X509Certificate; -import java.util.Date; - -import javax.security.auth.x500.X500Principal; - -import org.apache.brooklyn.core.internal.BrooklynInitialization; -import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; -import org.bouncycastle.asn1.x509.X509Extension; -import org.bouncycastle.jce.X509Principal; - -import brooklyn.util.exceptions.Exceptions; - -/** A fluent API which simplifies generating certificates (signed keys) */ -/* NB - re deprecation - we use deprecated X509V3CertificateGenerator still - * because the official replacement, X509v3CertificateBuilder, - * drags in an add'l dependency (bcmail) and is harder to use. */ -public class FluentKeySigner { - - static { BrooklynInitialization.initSecureKeysBouncyCastleProvider(); } - - protected X500Principal issuerPrincipal; - protected KeyPair issuerKey; - - protected SecureRandom srand = new SecureRandom(); - - protected Date validityStartDate, validityEndDate; - protected BigInteger serialNumber; - - protected String signatureAlgorithm = "MD5WithRSAEncryption"; - protected AuthorityKeyIdentifier authorityKeyIdentifier; - protected X509Certificate authorityCertificate; - - public FluentKeySigner(X500Principal issuerPrincipal, KeyPair issuerKey) { - this.issuerPrincipal = issuerPrincipal; - this.issuerKey = issuerKey; - validFromDaysAgo(7); - validForYears(10); - } - public FluentKeySigner(String issuerCommonName, KeyPair issuerKey) { - this(SecureKeys.getX500PrincipalWithCommonName(issuerCommonName), issuerKey); - } - - public FluentKeySigner(String issuerCommonName) { - this(issuerCommonName, SecureKeys.newKeyPair()); - } - - public FluentKeySigner(X509Certificate caCert, KeyPair caKey) { - this(caCert.getIssuerX500Principal(), caKey); - authorityCertificate(caCert); - } - - public KeyPair getKey() { - return issuerKey; - } - - public X500Principal getPrincipal() { - return issuerPrincipal; - } - - @SuppressWarnings("deprecation") - public String getCommonName() { -// TODO see deprecation note at top of file - // for modernising, would RFC4519Style.cn work ? - return (String) new X509Principal(issuerPrincipal.getName()).getValues(org.bouncycastle.asn1.x509.X509Name.CN).elementAt(0); - } - - public X509Certificate getAuthorityCertificate() { - return authorityCertificate; - } - - public FluentKeySigner validFromDaysAgo(long days) { - return validFrom(new Date( (System.currentTimeMillis() / (1000L*60*60*24) - days) * 1000L*60*60*24)); - } - - public FluentKeySigner validFrom(Date d) { - validityStartDate = d; - return this; - } - - public FluentKeySigner validForYears(long years) { - return validUntil(new Date( (System.currentTimeMillis() / (1000L*60*60*24) + 365*years) * 1000L*60*60*24)); - } - - public FluentKeySigner validUntil(Date d) { - validityEndDate = d; - return this; - } - - /** use a hard-coded serial number; or make one up, if null */ - public FluentKeySigner serialNumber(BigInteger serialNumber) { - this.serialNumber = serialNumber; - return this; - } - - public FluentKeySigner signatureAlgorithm(String signatureAlgorithm) { - this.signatureAlgorithm = signatureAlgorithm; - return this; - } - - @SuppressWarnings("deprecation") - public FluentKeySigner authorityCertificate(X509Certificate certificate) { - try { - authorityKeyIdentifier(new org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure(certificate)); - this.authorityCertificate = certificate; - return this; - } catch (CertificateParsingException e) { - throw Exceptions.propagate(e); - } - } - - public FluentKeySigner authorityKeyIdentifier(AuthorityKeyIdentifier authorityKeyIdentifier) { - this.authorityKeyIdentifier = authorityKeyIdentifier; - return this; - } - - public FluentKeySigner selfsign() { - if (authorityCertificate!=null) throw new IllegalStateException("Signer already has certificate"); - authorityCertificate(newCertificateFor(getCommonName(), getKey())); - return this; - } - - // TODO see note re deprecation at start of file - @SuppressWarnings("deprecation") - public X509Certificate newCertificateFor(X500Principal subject, PublicKey keyToCertify) { - try { - org.bouncycastle.x509.X509V3CertificateGenerator v3CertGen = new org.bouncycastle.x509.X509V3CertificateGenerator(); - - v3CertGen.setSerialNumber( - serialNumber != null ? serialNumber : - // must be positive - BigInteger.valueOf(srand.nextLong()).abs().add(BigInteger.ONE)); - v3CertGen.setIssuerDN(issuerPrincipal); - v3CertGen.setNotBefore(validityStartDate); - v3CertGen.setNotAfter(validityEndDate); - v3CertGen.setSignatureAlgorithm(signatureAlgorithm); - - v3CertGen.setSubjectDN(subject); - v3CertGen.setPublicKey(keyToCertify); - - v3CertGen.addExtension(X509Extension.subjectKeyIdentifier, false, - new org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure(keyToCertify)); - - if (authorityKeyIdentifier!=null) - v3CertGen.addExtension(X509Extension.authorityKeyIdentifier, false, - authorityKeyIdentifier); - - X509Certificate pkCertificate = v3CertGen.generate(issuerKey.getPrivate(), "BC"); - return pkCertificate; - - } catch (Exception e) { - throw Exceptions.propagate(e); - } - } - - public X509Certificate newCertificateFor(String commonName, PublicKey key) { -// SecureKeys.getX509PrincipalWithCommonName(commonName) - return newCertificateFor( - SecureKeys.getX500PrincipalWithCommonName(commonName) -// new X509Principal("CN=" + commonName + ", OU=None, O=None, L=None, C=None") - , key); - } - - public X509Certificate newCertificateFor(String commonName, KeyPair key) { - return newCertificateFor(commonName, key.getPublic()); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/brooklyn/util/crypto/SecureKeys.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/crypto/SecureKeys.java b/core/src/main/java/brooklyn/util/crypto/SecureKeys.java deleted file mode 100644 index 29fcf32..0000000 --- a/core/src/main/java/brooklyn/util/crypto/SecureKeys.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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 brooklyn.util.crypto; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; - -import org.apache.brooklyn.core.internal.BrooklynInitialization; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.jce.X509Principal; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMDecryptorProvider; -import org.bouncycastle.openssl.PEMEncryptedKeyPair; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.PEMWriter; -import org.bouncycastle.openssl.PasswordFinder; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.stream.Streams; - -import com.google.common.base.Objects; -import com.google.common.base.Throwables; - -/** - * Utility methods for generating and working with keys, - * extending the parent class with useful things provided by BouncyCastle crypto library. - * (Parent class is in a different project where BC is not included as a dependency.) - */ -public class SecureKeys extends SecureKeysWithoutBouncyCastle { - - private static final Logger log = LoggerFactory.getLogger(SecureKeys.class); - - static { BrooklynInitialization.initSecureKeysBouncyCastleProvider(); } - - public static void initBouncyCastleProvider() { - Security.addProvider(new BouncyCastleProvider()); - } - - public static class PassphraseProblem extends IllegalStateException { - private static final long serialVersionUID = -3382824813899223447L; - public PassphraseProblem(String message) { super("Passphrase problem with this key: "+message); } - public PassphraseProblem(String message, Exception cause) { super("Passphrase problem with this key: "+message, cause); } - } - - private SecureKeys() {} - - /** RFC1773 order, with None for other values. Normally prefer X500Principal. */ - public static X509Principal getX509PrincipalWithCommonName(String commonName) { - return new X509Principal("" + "C=None," + "L=None," + "O=None," + "OU=None," + "CN=" + commonName); - } - - /** reads RSA or DSA / pem style private key files (viz {@link #toPem(KeyPair)}), extracting also the public key if possible - * @throws IllegalStateException on errors, in particular {@link PassphraseProblem} if that is the problem */ - public static KeyPair readPem(InputStream input, final String passphrase) { - // TODO cache is only for fallback "reader" strategy (2015-01); delete when Parser confirmed working - byte[] cache = Streams.readFully(input); - input = new ByteArrayInputStream(cache); - - try { - PEMParser pemParser = new PEMParser(new InputStreamReader(input)); - - Object object = pemParser.readObject(); - pemParser.close(); - - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - KeyPair kp = null; - if (object==null) { - throw new IllegalStateException("PEM parsing failed: missing or invalid data"); - } else if (object instanceof PEMEncryptedKeyPair) { - if (passphrase==null) throw new PassphraseProblem("passphrase required"); - try { - PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray()); - kp = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - throw new PassphraseProblem("wrong passphrase", e); - } - } else if (object instanceof PEMKeyPair) { - kp = converter.getKeyPair((PEMKeyPair) object); - } else if (object instanceof PrivateKeyInfo) { - PrivateKey privKey = converter.getPrivateKey((PrivateKeyInfo) object); - kp = new KeyPair(null, privKey); - } else { - throw new IllegalStateException("PEM parser support missing for: "+object); - } - - return kp; - - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - - // older code relied on PEMReader, now deprecated - // replaced with above based on http://stackoverflow.com/questions/14919048/bouncy-castle-pemreader-pemparser - // passes the same tests (Jan 2015) but leaving the old code as a fallback for the time being - - input = new ByteArrayInputStream(cache); - try { - Security.addProvider(new BouncyCastleProvider()); - @SuppressWarnings("deprecation") - org.bouncycastle.openssl.PEMReader pr = new org.bouncycastle.openssl.PEMReader(new InputStreamReader(input), new PasswordFinder() { - public char[] getPassword() { - return passphrase!=null ? passphrase.toCharArray() : new char[0]; - } - }); - @SuppressWarnings("deprecation") - KeyPair result = (KeyPair) pr.readObject(); - pr.close(); - if (result==null) - throw Exceptions.propagate(e); - - log.warn("PEMParser failed when deprecated PEMReader succeeded, with "+result+"; had: "+e); - - return result; - - } catch (Exception e2) { - Exceptions.propagateIfFatal(e2); - throw Exceptions.propagate(e); - } - } - } - - /** because KeyPair.equals is not implemented :( */ - public static boolean equal(KeyPair k1, KeyPair k2) { - return Objects.equal(k2.getPrivate(), k1.getPrivate()) && Objects.equal(k2.getPublic(), k1.getPublic()); - } - - /** returns the PEM (base64, ie for id_rsa) string for the private key / key pair; - * this starts -----BEGIN PRIVATE KEY----- and ends similarly, like id_rsa. - * also see {@link #readPem(InputStream, String)} */ - public static String toPem(KeyPair key) { - return stringPem(key); - } - - /** returns id_rsa.pub style file, of public key */ - public static String toPub(KeyPair key) { - return AuthorizedKeysParser.encodePublicKey(key.getPublic()); - } - - /** opposite of {@link #toPub(KeyPair)}, given text */ - public static PublicKey fromPub(String pubText) { - return AuthorizedKeysParser.decodePublicKey(pubText); - } - - /** @deprecated since 0.7.0, use {@link #toPem(KeyPair)} */ @Deprecated - public static String stringPem(KeyPair key) { - try { - StringWriter sw = new StringWriter(); - PEMWriter w = new PEMWriter(sw); - w.writeObject(key); - w.close(); - return sw.toString(); - } catch (IOException e) { - throw Throwables.propagate(e); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/brooklyn/util/file/ArchiveBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/file/ArchiveBuilder.java b/core/src/main/java/brooklyn/util/file/ArchiveBuilder.java deleted file mode 100644 index 6985406..0000000 --- a/core/src/main/java/brooklyn/util/file/ArchiveBuilder.java +++ /dev/null @@ -1,423 +0,0 @@ -/* - * 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 brooklyn.util.file; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Collections; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipOutputStream; - -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.file.ArchiveUtils.ArchiveType; -import brooklyn.util.os.Os; - -import com.google.common.annotations.Beta; -import com.google.common.collect.Iterables; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.io.Files; - -/** - * Build a Zip or Jar archive. - *

- * Supports creating temporary archives that will be deleted on exit, if no name is - * specified. The created file must be a Java archive type, with the extension {@code .zip}, - * {@code .jar}, {@code .war} or {@code .ear}. - *

- * Example: - *

 File zip = ArchiveBuilder.archive("data/archive.zip")
- *         .addAt(new File("./pom.xml"), "")
- *         .addDirContentsAt(new File("./src"), "src/")
- *         .addAt(new File("/tmp/Extra.java"), "src/main/java/")
- *         .addDirContentsAt(new File("/tmp/overlay/"), "")
- *         .create();
- * 
- *

- */ -@Beta -public class ArchiveBuilder { - - /** - * Create an {@link ArchiveBuilder} for an archive with the given name. - */ - public static ArchiveBuilder archive(String archive) { - return new ArchiveBuilder(archive); - } - - /** - * Create an {@link ArchiveBuilder} for a {@link ArchiveType#ZIP Zip} format archive. - */ - public static ArchiveBuilder zip() { - return new ArchiveBuilder(ArchiveType.ZIP); - } - - /** - * Create an {@link ArchiveBuilder} for a {@link ArchiveType#JAR Jar} format archive. - */ - public static ArchiveBuilder jar() { - return new ArchiveBuilder(ArchiveType.JAR); - } - - // TODO would be nice to support TAR and TGZ - // e.g. using commons-compress - // TarArchiveOutputStream out = new TarArchiveOutputStream(new GZIPOutputStream(bytes)); - // but I think the way entries are done is slightly different so we'd need a bit of refactoring - - private final ArchiveType type; - private File archive; - private Manifest manifest; - private Multimap entries = LinkedHashMultimap.create(); - - private ArchiveBuilder() { - this(ArchiveType.ZIP); - } - - private ArchiveBuilder(String filename) { - this(ArchiveType.of(filename)); - - named(filename); - } - - private ArchiveBuilder(ArchiveType type) { - checkNotNull(type); - checkArgument(ArchiveType.ZIP_ARCHIVES.contains(type)); - - this.type = type; - this.manifest = new Manifest(); - } - - /** - * Set the location of the generated archive file. - */ - public ArchiveBuilder named(String name) { - checkNotNull(name); - String ext = Files.getFileExtension(name); - if (ext.isEmpty()) { - name = name + "." + type.toString(); - } else if (type != ArchiveType.of(name)) { - throw new IllegalArgumentException(String.format("Extension for '%s' did not match archive type of %s", ext, type)); - } - this.archive = new File(Os.tidyPath(name)); - return this; - } - - /** - * @see #named(String) - */ - public ArchiveBuilder named(File file) { - checkNotNull(file); - return named(file.getPath()); - } - - /** - * Add a manifest entry with the given {@code key} and {@code value}. - */ - public ArchiveBuilder manifest(Object key, Object value) { - checkNotNull(key, "key"); - checkNotNull(value, "value"); - manifest.getMainAttributes().put(key, value); - return this; - } - - /** - * Add the file located at the {@code filePath} to the archive, - * with some complicated base-name strategies. - * - * @deprecated since 0.7.0 use one of the other add methods which makes the strategy explicit */ @Deprecated - public ArchiveBuilder add(String filePath) { - checkNotNull(filePath, "filePath"); - return add(new File(Os.tidyPath(filePath))); - } - - /** - * Add the {@code file} to the archive. - *

- * If the file path is absolute, or points to a file above the current directory, - * the file is added to the archive as a top-level entry, using the file name only. - * For relative {@code filePath}s below the current directory, the file is added - * using the path given and is assumed to be located relative to the current - * working directory. - *

- * No checks for file existence are made at this stage. - * - * @see #entry(String, File) - * @deprecated since 0.7.0 use one of the other add methods which makes the strategy explicit */ @Deprecated - public ArchiveBuilder add(File file) { - checkNotNull(file, "file"); - String filePath = Os.tidyPath(file.getPath()); - if (file.isAbsolute() || filePath.startsWith("../")) { - return entry(Os.mergePaths(".", file.getName()), file); - } else { - return entry(Os.mergePaths(".", filePath), file); - } - } - - /** - * Add the file located at the {@code fileSubPath}, relative to the {@code baseDir} on the local system, - * to the archive. - *

- * Uses the {@code fileSubPath} as the name of the file in the archive. Note that the - * file is found by concatenating the two path components using {@link Os#mergePaths(String...)}, - * thus {@code fileSubPath} should not be absolute or point to a location above the current directory. - *

- * Use {@link #entry(String, String)} directly or {@link #entries(Map)} for complete - * control over file locations and names in the archive. - * - * @see #entry(String, String) - */ - public ArchiveBuilder addFromLocalBaseDir(File baseDir, String fileSubPath) { - checkNotNull(baseDir, "baseDir"); - checkNotNull(fileSubPath, "filePath"); - return entry(Os.mergePaths(".", fileSubPath), Os.mergePaths(baseDir.getPath(), fileSubPath)); - } - /** @deprecated since 0.7.0 use {@link #addFromLocalBaseDir(File, String)}, or - * one of the other add methods if adding relative to baseDir was not intended */ @Deprecated - public ArchiveBuilder addFromLocalBaseDir(String baseDir, String fileSubPath) { - return addFromLocalBaseDir(new File(baseDir), fileSubPath); - } - /** @deprecated since 0.7.0 use {@link #addFromLocalBaseDir(File, String)}, or - * one of the other add methods if adding relative to baseDir was not intended */ @Deprecated - public ArchiveBuilder add(String baseDir, String fileSubPath) { - return addFromLocalBaseDir(baseDir, fileSubPath); - } - - /** adds the given file to the archive, preserving its name but putting under the given directory in the archive (may be "" or "./") */ - public ArchiveBuilder addAt(File file, String archiveParentDir) { - checkNotNull(archiveParentDir, "archiveParentDir"); - checkNotNull(file, "file"); - return entry(Os.mergePaths(archiveParentDir, file.getName()), file); - } - - /** - * Add the contents of the directory named {@code dirName} to the archive. - * - * @see #addDir(File) - * @deprecated since 0.7.0 use {@link #addDirContentsAt(File, String) */ @Deprecated - public ArchiveBuilder addDir(String dirName) { - checkNotNull(dirName, "dirName"); - return addDir(new File(Os.tidyPath(dirName))); - } - - /** - * Add the contents of the directory {@code dir} to the archive. - * The directory's name is not included; use {@link #addAtRoot(File)} if you want that behaviour. - *

- * Uses {@literal .} as the parent directory name for the contents. - * - * @see #entry(String, File) - */ - public ArchiveBuilder addDirContentsAt(File dir, String archiveParentDir) { - checkNotNull(dir, "dir"); - if (!dir.isDirectory()) throw new IllegalArgumentException(dir+" is not a directory; cannot add contents to archive"); - return entry(archiveParentDir, dir); - } - /** - * As {@link #addDirContentsAt(File, String)}, - * using {@literal .} as the parent directory name for the contents. - * - * @deprecated since 0.7.0 use {@link #addDirContentsAt(File, String) - * to clarify API, argument types, and be explicit about where it should be installed, - * because JARs seem to require "" whereas ZIPs might want "./". */ @Deprecated - public ArchiveBuilder addDir(File dir) { - return addDirContentsAt(dir, "."); - } - - /** - * Add the collection of {@code files} to the archive. - * - * @see #add(String) - * @deprecated since 0.7.0 use one of the other add methods if keeping this file's path was not intended */ @Deprecated - public ArchiveBuilder add(Iterable files) { - checkNotNull(files, "files"); - for (String filePath : files) { - add(filePath); - } - return this; - } - - /** - * Add the collection of {@code files}, relative to the {@code baseDir}, to - * the archive. - * - * @see #add(String, String) - * @deprecated since 0.7.0 use one of the other add methods if keeping this file's path was not intended */ @Deprecated - public ArchiveBuilder add(String baseDir, Iterable files) { - checkNotNull(baseDir, "baseDir"); - checkNotNull(files, "files"); - for (String filePath : files) { - add(baseDir, filePath); - } - return this; - } - - /** - * Add the {@code file} to the archive with the path {@code entryPath}. - * - * @see #entry(String, File) - */ - public ArchiveBuilder entry(String entryPath, String filePath) { - checkNotNull(entryPath, "entryPath"); - checkNotNull(filePath, "filePath"); - return entry(entryPath, new File(filePath)); - } - - /** - * Add the {@code file} to the archive with the path {@code entryPath}. - */ - public ArchiveBuilder entry(String entryPath, File file) { - checkNotNull(entryPath, "entryPath"); - checkNotNull(file, "file"); - this.entries.put(entryPath, file); - return this; - } - - /** - * Add a {@link Map} of entries to the archive. - *

- * The keys should be the names of the file entries to be added to the archive and - * the value should point to the actual {@link File} to be added. - *

- * This allows complete control over the directory structure of the eventual archive, - * as the entry names do not need to bear any relationship to the name or location - * of the files on the filesystem. - */ - public ArchiveBuilder entries(Map entries) { - checkNotNull(entries, "entries"); - for (Map.Entry entry: entries.entrySet()) - this.entries.put(entry.getKey(), entry.getValue()); - return this; - } - - /** - * Generates the archive and outputs it to the given stream, ignoring any file name. - *

- * This will add a manifest file if the type is a Jar archive. - */ - public void stream(OutputStream output) { - try { - ZipOutputStream target; - if (type == ArchiveType.ZIP) { - target = new ZipOutputStream(output); - } else { - manifest(Attributes.Name.MANIFEST_VERSION, "1.0"); - target = new JarOutputStream(output, manifest); - } - for (String entry : entries.keySet()) { - addToArchive(entry, entries.get(entry), target); - } - target.close(); - } catch (IOException ioe) { - throw Exceptions.propagate(ioe); - } - } - - /** - * Generates the archive, saving it with the given name. - */ - public File create(String archiveFile) { - return named(archiveFile).create(); - } - - /** - * Generates the archive. - *

- * If no name has been specified, the archive will be created as a temporary file with - * a unique name, that is deleted on exit. Otherwise, the given name will be used. - */ - public File create() { - if (archive == null) { - File temp = Os.newTempFile("brooklyn-archive", type.toString()); - temp.deleteOnExit(); - named(temp); - } - try { - OutputStream output = new FileOutputStream(archive); - stream(output); - output.close(); - } catch (IOException ioe) { - throw Exceptions.propagate(ioe); - } - return archive; - } - - /** - * Recursively add files to the archive. - *

- * Code adapted from this example - *

- * Note {@link File} provides no support for symbolic links, and as such there is - * no way to ensure that a symbolic link to a directory is not followed when traversing the - * tree. In this case, iterables created by this traverser could contain files that are - * outside of the given directory or even be infinite if there is a symbolic link loop. - */ - private void addToArchive(String path, Iterable sources, ZipOutputStream target) throws IOException { - int size = Iterables.size(sources); - if (size==0) return; - boolean isDirectory; - if (size>1) { - // it must be directories if we are putting multiple things here - isDirectory = true; - } else { - isDirectory = Iterables.getOnlyElement(sources).isDirectory(); - } - - String name = path.replace("\\", "/"); - if (isDirectory) { - name += "/"; - JarEntry entry = new JarEntry(name); - - long lastModified=-1; - for (File source: sources) - if (source.lastModified()>lastModified) - lastModified = source.lastModified(); - - entry.setTime(lastModified); - target.putNextEntry(entry); - target.closeEntry(); - - for (File source: sources) { - if (!source.isDirectory()) { - throw new IllegalStateException("Cannot add multiple items at a path in archive unless they are directories: "+sources+" at "+path+" is not valid."); - } - Iterable children = Files.fileTreeTraverser().children(source); - for (File child : children) { - addToArchive(Os.mergePaths(path, child.getName()), Collections.singleton(child), target); - } - } - return; - } - - File source = Iterables.getOnlyElement(sources); - JarEntry entry = new JarEntry(name); - entry.setTime(source.lastModified()); - target.putNextEntry(entry); - Files.asByteSource(source).copyTo(target); - target.closeEntry(); - } -}