http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigUtils.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigUtils.java new file mode 100644 index 0000000..ce3c444 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigUtils.java @@ -0,0 +1,559 @@ +/* + * 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.brooklyn.location.basic; + +import static brooklyn.util.JavaGroovyEquivalents.groovyTruth; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.internal.BrooklynFeatureEnablement; +import org.apache.brooklyn.location.cloud.CloudLocationConfig; +import brooklyn.util.ResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.collections.MutableSet; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.crypto.AuthorizedKeysParser; +import brooklyn.util.crypto.SecureKeys; +import brooklyn.util.crypto.SecureKeys.PassphraseProblem; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.os.Os; +import brooklyn.util.text.StringFunctions; +import brooklyn.util.text.Strings; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class LocationConfigUtils { + + private static final Logger log = LoggerFactory.getLogger(LocationConfigUtils.class); + + /** Creates an instance of {@link OsCredential} by inspecting {@link LocationConfigKeys#PASSWORD}; + * {@link LocationConfigKeys#PRIVATE_KEY_DATA} and {@link LocationConfigKeys#PRIVATE_KEY_FILE}; + * {@link LocationConfigKeys#PRIVATE_KEY_PASSPHRASE} if needed, and + * {@link LocationConfigKeys#PRIVATE_KEY_DATA} and {@link LocationConfigKeys#PRIVATE_KEY_FILE} + * (defaulting to the private key file + ".pub"). + **/ + public static OsCredential getOsCredential(ConfigBag config) { + return OsCredential.newInstance(config); + } + + /** Convenience class for holding private/public keys and passwords, inferring from config keys. + * See {@link LocationConfigUtils#getOsCredential(ConfigBag)}. */ + @Beta // would be nice to replace with a builder pattern + public static class OsCredential { + private final ConfigBag config; + private boolean preferPassword = false; + private boolean tryDefaultKeys = true; + private boolean requirePublicKey = true; + private boolean doKeyValidation = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_VALIDATE_LOCATION_SSH_KEYS); + private boolean warnOnErrors = true; + private boolean throwOnErrors = false; + + private boolean dirty = true;; + + private String privateKeyData; + private String publicKeyData; + private String password; + + private OsCredential(ConfigBag config) { + this.config = config; + } + + /** throws if there are any problems */ + public OsCredential checkNotEmpty() { + checkNoErrors(); + + if (!hasKey() && !hasPassword()) { + if (warningMessages.size()>0) + throw new IllegalStateException("Could not find credentials: "+warningMessages); + else + throw new IllegalStateException("Could not find credentials"); + } + return this; + } + + /** throws if there were errors resolving (e.g. explicit keys, none of which were found/valid, or public key required and not found) + * @return */ + public OsCredential checkNoErrors() { + throwOnErrors(true); + dirty(); + infer(); + return this; + } + + public OsCredential logAnyWarnings() { + if (!warningMessages.isEmpty()) + log.warn("When reading credentials: "+warningMessages); + return this; + } + + public Set<String> getWarningMessages() { + return warningMessages; + } + + /** returns either the key or password or null; if both a key and a password this prefers the key unless otherwise set + * via {@link #preferPassword()} */ + public synchronized String getPreferredCredential() { + infer(); + + if (isUsingPassword()) return password; + if (hasKey()) return privateKeyData; + return null; + } + + /** if there is no credential (ignores public key) */ + public boolean isEmpty() { + return !hasKey() && !hasPassword(); + } + public boolean hasKey() { + infer(); + // key has stricter non-blank check than password + return Strings.isNonBlank(privateKeyData); + } + public boolean hasPassword() { + infer(); + // blank, even empty passwords are allowed + return password!=null; + } + /** if a password is available, and either this is preferred over a key or there is no key */ + public boolean isUsingPassword() { + return hasPassword() && (!hasKey() || preferPassword); + } + + public String getPrivateKeyData() { + infer(); + return privateKeyData; + } + public String getPublicKeyData() { + infer(); + return publicKeyData; + } + public String getPassword() { + infer(); + return password; + } + + /** if both key and password supplied, prefer the key; the default */ + public OsCredential preferKey() { preferPassword = false; return dirty(); } + /** if both key and password supplied, prefer the password; see {@link #preferKey()} */ + public OsCredential preferPassword() { preferPassword = true; return dirty(); } + + /** if false, do not mind if there is no public key corresponding to any private key; + * defaults to true; only applies if a private key is set */ + public OsCredential requirePublicKey(boolean requirePublicKey) { + this.requirePublicKey = requirePublicKey; + return dirty(); + } + /** whether to check the private/public keys and passphrase are coherent; default true */ + public OsCredential doKeyValidation(boolean doKeyValidation) { + this.doKeyValidation = doKeyValidation; + return dirty(); + } + /** if true (the default) this will look at default locations set on keys */ + public OsCredential useDefaultKeys(boolean tryDefaultKeys) { + this.tryDefaultKeys = tryDefaultKeys; + return dirty(); + } + /** whether to log warnings on problems */ + public OsCredential warnOnErrors(boolean warnOnErrors) { + this.warnOnErrors = warnOnErrors; + return dirty(); + } + /** whether to throw on problems */ + public OsCredential throwOnErrors(boolean throwOnErrors) { + this.throwOnErrors = throwOnErrors; + return dirty(); + } + + private OsCredential dirty() { dirty = true; return this; } + + public static OsCredential newInstance(ConfigBag config) { + return new OsCredential(config); + } + + private synchronized void infer() { + if (!dirty) return; + warningMessages.clear(); + + log.debug("Inferring OS credentials"); + privateKeyData = config.get(LocationConfigKeys.PRIVATE_KEY_DATA); + password = config.get(LocationConfigKeys.PASSWORD); + publicKeyData = getKeyDataFromDataKeyOrFileKey(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE); + + KeyPair privateKey = null; + + if (Strings.isBlank(privateKeyData)) { + // look up private key files + String privateKeyFiles = null; + boolean privateKeyFilesExplicitlySet = config.containsKey(LocationConfigKeys.PRIVATE_KEY_FILE); + if (privateKeyFilesExplicitlySet || (tryDefaultKeys && password==null)) + privateKeyFiles = config.get(LocationConfigKeys.PRIVATE_KEY_FILE); + if (Strings.isNonBlank(privateKeyFiles)) { + Iterator<String> fi = Arrays.asList(privateKeyFiles.split(File.pathSeparator)).iterator(); + while (fi.hasNext()) { + String file = fi.next(); + if (Strings.isNonBlank(file)) { + try { + // real URL's won't actual work, due to use of path separator above + // not real important, but we get it for free if "files" is a list instead. + // using ResourceUtils is useful for classpath resources + if (file!=null) + privateKeyData = ResourceUtils.create().getResourceAsString(file); + // else use data already set + + privateKey = getValidatedPrivateKey(file); + + if (privateKeyData==null) { + // was cleared due to validation error + } else if (Strings.isNonBlank(publicKeyData)) { + log.debug("Loaded private key data from "+file+" (public key data explicitly set)"); + break; + } else { + String publicKeyFile = (file!=null ? file+".pub" : "(data)"); + try { + publicKeyData = ResourceUtils.create().getResourceAsString(publicKeyFile); + + log.debug("Loaded private key data from "+file+ + " and public key data from "+publicKeyFile); + break; + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.debug("No public key file "+publicKeyFile+"; will try extracting from private key"); + publicKeyData = AuthorizedKeysParser.encodePublicKey(privateKey.getPublic()); + + if (publicKeyData==null) { + if (requirePublicKey) { + addWarning("Unable to find or extract public key for "+file, "skipping"); + } else { + log.debug("Loaded private key data from "+file+" (public key data not found but not required)"); + break; + } + } else { + log.debug("Loaded private key data from "+file+" (public key data extracted)"); + break; + } + privateKeyData = null; + } + } + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + String message = "Missing/invalid private key file "+file; + if (privateKeyFilesExplicitlySet) addWarning(message, (!fi.hasNext() ? "no more files to try" : "trying next file")+": "+e); + } + } + } + if (privateKeyFilesExplicitlySet && Strings.isBlank(privateKeyData)) + error("No valid private keys found", ""+warningMessages); + } + } else { + privateKey = getValidatedPrivateKey("(data)"); + } + + if (privateKeyData!=null) { + if (requirePublicKey && Strings.isBlank(publicKeyData)) { + if (privateKey!=null) { + publicKeyData = AuthorizedKeysParser.encodePublicKey(privateKey.getPublic()); + } + if (Strings.isBlank(publicKeyData)) { + error("If explicit "+LocationConfigKeys.PRIVATE_KEY_DATA.getName()+" is supplied, then " + + "the corresponding "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" must also be supplied.", null); + } else { + log.debug("Public key data extracted"); + } + } + if (doKeyValidation && privateKey!=null && privateKey.getPublic()!=null && Strings.isNonBlank(publicKeyData)) { + PublicKey decoded = null; + try { + decoded = AuthorizedKeysParser.decodePublicKey(publicKeyData); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + addWarning("Invalid public key: "+decoded); + } + if (decoded!=null && !privateKey.getPublic().equals( decoded )) { + error("Public key inferred from does not match public key extracted from private key", null); + } + } + } + + log.debug("OS credential inference: "+this); + dirty = false; + } + + private KeyPair getValidatedPrivateKey(String label) { + KeyPair privateKey = null; + String passphrase = config.get(CloudLocationConfig.PRIVATE_KEY_PASSPHRASE); + try { + privateKey = SecureKeys.readPem(new ByteArrayInputStream(privateKeyData.getBytes()), passphrase); + if (passphrase!=null) { + // get the unencrypted key data for our internal use (jclouds requires this) + privateKeyData = SecureKeys.toPem(privateKey); + } + } catch (PassphraseProblem e) { + if (doKeyValidation) { + log.debug("Encountered error handling key "+label+": "+e, e); + if (Strings.isBlank(passphrase)) + addWarning("Passphrase required for key '"+label+"'"); + else + addWarning("Invalid passphrase for key '"+label+"'"); + privateKeyData = null; + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (doKeyValidation) { + addWarning("Unable to parse private key from '"+label+"': unknown format"); + privateKeyData = null; + } + } + return privateKey; + } + + Set<String> warningMessages = MutableSet.of(); + + private void error(String msg, String logExtension) { + addWarning(msg); + if (warnOnErrors) log.warn(msg+(logExtension==null ? "" : ": "+logExtension)); + if (throwOnErrors) throw new IllegalStateException(msg+(logExtension==null ? "" : "; "+logExtension)); + } + + private void addWarning(String msg) { + addWarning(msg, null); + } + private void addWarning(String msg, String debugExtension) { + log.debug(msg+(debugExtension==null ? "" : "; "+debugExtension)); + warningMessages.add(msg); + } + + @Override + public String toString() { + return getClass().getSimpleName()+"["+ + (Strings.isNonBlank(publicKeyData) ? publicKeyData : "no-public-key")+";"+ + (Strings.isNonBlank(privateKeyData) ? "private-key-present" : "no-private-key")+","+ + (password!=null ? "password(len="+password.length()+")" : "no-password")+"]"; + } + } + + /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated + public static String getPrivateKeyData(ConfigBag config) { + return getKeyData(config, LocationConfigKeys.PRIVATE_KEY_DATA, LocationConfigKeys.PRIVATE_KEY_FILE); + } + + /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated + public static String getPublicKeyData(ConfigBag config) { + String data = getKeyData(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE); + if (groovyTruth(data)) return data; + + String privateKeyFile = config.get(LocationConfigKeys.PRIVATE_KEY_FILE); + if (groovyTruth(privateKeyFile)) { + List<String> privateKeyFiles = Arrays.asList(privateKeyFile.split(File.pathSeparator)); + List<String> publicKeyFiles = ImmutableList.copyOf(Iterables.transform(privateKeyFiles, StringFunctions.append(".pub"))); + List<String> publicKeyFilesTidied = tidyFilePaths(publicKeyFiles); + + String fileData = getFileContents(publicKeyFilesTidied); + if (groovyTruth(fileData)) { + if (log.isDebugEnabled()) log.debug("Loaded "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" from inferred files, based on "+LocationConfigKeys.PRIVATE_KEY_FILE.getName() + ": used " + publicKeyFilesTidied + " for "+config.getDescription()); + config.put(LocationConfigKeys.PUBLIC_KEY_DATA, fileData); + return fileData; + } else { + log.info("Not able to load "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" from inferred files, based on "+LocationConfigKeys.PRIVATE_KEY_FILE.getName() + ": tried " + publicKeyFilesTidied + " for "+config.getDescription()); + } + } + + return null; + } + + /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated + public static String getKeyData(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey) { + return getKeyDataFromDataKeyOrFileKey(config, dataKey, fileKey); + } + + private static String getKeyDataFromDataKeyOrFileKey(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey) { + boolean unused = config.isUnused(dataKey); + String data = config.get(dataKey); + if (groovyTruth(data) && !unused) { + return data; + } + + String file = config.get(fileKey); + if (groovyTruth(file)) { + List<String> files = Arrays.asList(file.split(File.pathSeparator)); + List<String> filesTidied = tidyFilePaths(files); + String fileData = getFileContents(filesTidied); + if (fileData == null) { + log.warn("Invalid file" + (files.size() > 1 ? "s" : "") + " for " + fileKey + " (given " + files + + (files.equals(filesTidied) ? "" : "; converted to " + filesTidied) + ") " + + "may fail provisioning " + config.getDescription()); + } else if (groovyTruth(data)) { + if (!fileData.trim().equals(data.trim())) + log.warn(dataKey.getName()+" and "+fileKey.getName()+" both specified; preferring the former"); + } else { + data = fileData; + config.put(dataKey, data); + config.get(dataKey); + } + } + + return data; + } + + /** + * Reads the given file(s) in-order, returning the contents of the first file that can be read. + * Returns the file contents, or null if none of the files can be read. + * + * @param files list of file paths + */ + private static String getFileContents(Iterable<String> files) { + Iterator<String> fi = files.iterator(); + while (fi.hasNext()) { + String file = fi.next(); + if (groovyTruth(file)) { + try { + // see comment above + String result = ResourceUtils.create().getResourceAsString(file); + if (result!=null) return result; + log.debug("Invalid file "+file+" ; " + (!fi.hasNext() ? "no more files to try" : "trying next file")+" (null)"); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.debug("Invalid file "+file+" ; " + (!fi.hasNext() ? "no more files to try" : "trying next file"), e); + } + } + } + return null; + } + + private static List<String> tidyFilePaths(Iterable<String> files) { + List<String> result = Lists.newArrayList(); + for (String file : files) { + result.add(Os.tidyPath(file)); + } + return result; + } + + /** @deprecated since 0.6.0 use configBag.getWithDeprecation */ + @Deprecated + @SuppressWarnings("unchecked") + public static <T> T getConfigCheckingDeprecatedAlternatives(ConfigBag configBag, ConfigKey<T> preferredKey, + ConfigKey<?> ...deprecatedKeys) { + T value1 = (T) configBag.getWithDeprecation(preferredKey, deprecatedKeys); + T value2 = getConfigCheckingDeprecatedAlternativesInternal(configBag, preferredKey, deprecatedKeys); + if (!Objects.equal(value1, value2)) { + // points to a bug in one of the get-with-deprecation methods + log.warn("Deprecated getConfig with deprecated keys "+Arrays.toString(deprecatedKeys)+" gets different value with " + + "new strategy "+preferredKey+" ("+value1+") and old ("+value2+"); preferring old value for now, but this behaviour will change"); + return value2; + } + return value1; + } + + @SuppressWarnings("unchecked") + private static <T> T getConfigCheckingDeprecatedAlternativesInternal(ConfigBag configBag, ConfigKey<T> preferredKey, + ConfigKey<?> ...deprecatedKeys) { + ConfigKey<?> keyProvidingValue = null; + T value = null; + boolean found = false; + if (configBag.containsKey(preferredKey)) { + value = configBag.get(preferredKey); + found = true; + keyProvidingValue = preferredKey; + } + + for (ConfigKey<?> deprecatedKey: deprecatedKeys) { + T altValue = null; + boolean altFound = false; + if (configBag.containsKey(deprecatedKey)) { + altValue = (T) configBag.get(deprecatedKey); + altFound = true; + + if (altFound) { + if (found) { + if (Objects.equal(value, altValue)) { + // fine -- nothing + } else { + log.warn("Detected deprecated key "+deprecatedKey+" with value "+altValue+" used in addition to "+keyProvidingValue+" " + + "with value "+value+" for "+configBag.getDescription()+"; ignoring"); + configBag.remove(deprecatedKey); + } + } else { + log.warn("Detected deprecated key "+deprecatedKey+" with value "+altValue+" used instead of recommended "+preferredKey+"; " + + "promoting to preferred key status; will not be supported in future versions"); + configBag.put(preferredKey, altValue); + configBag.remove(deprecatedKey); + value = altValue; + found = true; + keyProvidingValue = deprecatedKey; + } + } + } + } + + if (found) { + return value; + } else { + return configBag.get(preferredKey); // get the default + } + } + + public static Map<ConfigKey<String>,String> finalAndOriginalSpecs(String finalSpec, Object ...sourcesForOriginalSpec) { + // yuck!: TODO should clean up how these things get passed around + Map<ConfigKey<String>,String> result = MutableMap.of(); + if (finalSpec!=null) + result.put(LocationInternal.FINAL_SPEC, finalSpec); + + String originalSpec = null; + for (Object source: sourcesForOriginalSpec) { + if (source instanceof CharSequence) originalSpec = source.toString(); + else if (source instanceof Map) { + if (originalSpec==null) originalSpec = Strings.toString( ((Map<?,?>)source).get(LocationInternal.ORIGINAL_SPEC) ); + if (originalSpec==null) originalSpec = Strings.toString( ((Map<?,?>)source).get(LocationInternal.ORIGINAL_SPEC.getName()) ); + } + if (originalSpec!=null) break; + } + if (originalSpec==null) originalSpec = finalSpec; + if (originalSpec!=null) + result.put(LocationInternal.ORIGINAL_SPEC, originalSpec); + + return result; + } + + public static boolean isEnabled(ManagementContext mgmt, String prefix) { + ConfigKey<Boolean> key = ConfigKeys.newConfigKeyWithPrefix(prefix+".", LocationConfigKeys.ENABLED); + Boolean enabled = mgmt.getConfig().getConfig(key); + if (enabled!=null) return enabled.booleanValue(); + return true; + } + + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationDynamicType.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationDynamicType.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationDynamicType.java new file mode 100644 index 0000000..a289090 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationDynamicType.java @@ -0,0 +1,39 @@ +/* + * 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.brooklyn.location.basic; + +import brooklyn.basic.BrooklynDynamicType; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationType; + +public class LocationDynamicType extends BrooklynDynamicType<Location, AbstractLocation> { + + public LocationDynamicType(AbstractLocation location) { + super(location); + } + + public LocationType getSnapshot() { + return (LocationType) super.getSnapshot(); + } + + @Override + protected LocationTypeSnapshot newSnapshot() { + return new LocationTypeSnapshot(name, value(configKeys)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationInternal.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationInternal.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationInternal.java new file mode 100644 index 0000000..429e671 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationInternal.java @@ -0,0 +1,94 @@ +/* + * 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.brooklyn.location.basic; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.rebind.RebindSupport; +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.mementos.LocationMemento; + +import brooklyn.basic.BrooklynObjectInternal; +import brooklyn.config.ConfigInheritance; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.util.config.ConfigBag; + +import com.google.common.annotations.Beta; + +/** + * Information about locations private to Brooklyn. + */ +public interface LocationInternal extends BrooklynObjectInternal, Location { + + @Beta + public static final ConfigKey<String> ORIGINAL_SPEC = ConfigKeys.newStringConfigKey("spec.original", "The original spec used to instantiate a location"); + @Beta + public static final ConfigKey<String> FINAL_SPEC = ConfigKeys.newStringConfigKey("spec.final", "The actual spec (in a chain) which instantiates a location"); + @Beta + public static final ConfigKey<String> NAMED_SPEC_NAME = ConfigKeys.newStringConfigKey("spec.named.name", "The name on the (first) named spec in a chain"); + + /** + * Registers the given extension for the given type. If an extension already existed for + * this type, then this will override it. + * + * @throws NullPointerException if extensionType or extension are null + * @throws IllegalArgumentException if extension does not implement extensionType + */ + <T> void addExtension(Class<T> extensionType, T extension); + + /** + * Get a record of the metadata of this location. + * <p/> + * <p>Metadata records are used to record an audit trail of events relating to location usage + * (for billing purposes, for example). Implementations (and subclasses) should override this + * method to return information useful for this purpose.</p> + * + * @return + */ + public Map<String, String> toMetadataRecord(); + + /** + * @deprecated since 0.7.0; use {@link #config()}, such as {@code ((LocationInternal)location).config().getLocalBag()} + */ + @Deprecated + ConfigBag getLocalConfigBag(); + + /** + * Returns all config, including that inherited from parents. + * + * This method does not respect {@link ConfigInheritance} and so usage is discouraged. + * + * @deprecated since 0.7.0; use {@link #config()}, such as {@code ((LocationInternal)location).config().getBag()} + */ + @Deprecated + ConfigBag getAllConfigBag(); + + /** + * Users are strongly discouraged from calling or overriding this method. + * It is for internal calls only, relating to persisting/rebinding entities. + * This method may change (or be removed) in a future release without notice. + */ + @Override + @Beta + RebindSupport<LocationMemento> getRebindSupport(); + + ManagementContext getManagementContext(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationPredicates.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationPredicates.java new file mode 100644 index 0000000..d0b781b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationPredicates.java @@ -0,0 +1,108 @@ +/* + * 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.brooklyn.location.basic; + +import javax.annotation.Nullable; + +import brooklyn.config.ConfigKey; +import brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.location.Location; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; + +public class LocationPredicates { + + public static <T> Predicate<Location> idEqualTo(final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getId(), val); + } + }; + } + + public static <T> Predicate<Location> displayNameEqualTo(final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getDisplayName(), val); + } + }; + } + + public static <T> Predicate<Location> configEqualTo(final ConfigKey<T> configKey, final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + public static <T> Predicate<Location> configEqualTo(final HasConfigKey<T> configKey, final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + /** + * Returns a predicate that determines if a given location is a direct child of this {@code parent}. + */ + public static <T> Predicate<Location> isChildOf(final Location parent) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getParent(), parent); + } + }; + } + + /** + * Returns a predicate that determines if a given location is a descendant of this {@code ancestor}. + */ + public static <T> Predicate<Location> isDescendantOf(final Location ancestor) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + // assumes impossible to have cycles in location-hierarchy + Location contenderAncestor = (input == null) ? input : input.getParent(); + while (contenderAncestor != null) { + if (Objects.equal(contenderAncestor, ancestor)) { + return true; + } + contenderAncestor = contenderAncestor.getParent(); + } + return false; + } + }; + } + + public static <T> Predicate<Location> managed() { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Locations.isManaged(input); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynProperties.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynProperties.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynProperties.java new file mode 100644 index 0000000..e1feb18 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationPropertiesFromBrooklynProperties.java @@ -0,0 +1,224 @@ +/* + * 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.brooklyn.location.basic; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.File; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.BrooklynServerConfig; +import brooklyn.config.ConfigUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.internal.ssh.SshTool; +import brooklyn.util.os.Os; + +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; + +/** + * The properties to use for locations, loaded from brooklyn.properties file. + * + * @author aledsage + **/ +public class LocationPropertiesFromBrooklynProperties { + + private static final Logger LOG = LoggerFactory.getLogger(LocationPropertiesFromBrooklynProperties.class); + + @SuppressWarnings("deprecation") + protected static final Map<String, String> DEPRECATED_KEYS_MAPPING = new DeprecatedKeysMappingBuilder(LOG) + .camelToHyphen(LocationConfigKeys.DISPLAY_NAME) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_FILE) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_DATA) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_PASSPHRASE) + .camelToHyphen(LocationConfigKeys.PUBLIC_KEY_FILE) + .camelToHyphen(LocationConfigKeys.PUBLIC_KEY_DATA) + .camelToHyphen(LocationConfigKeys.CALLER_CONTEXT) + .build(); + + /** + * Finds the properties that apply to location, stripping off the prefixes. + * + * Order of preference (in ascending order) is: + * <ol> + * <li>brooklyn.location.* + * <li>brooklyn.location.provider.* + * <li>brooklyn.location.named.namedlocation.* + * </ol> + * <p> + * Converts deprecated hyphenated properties to the non-deprecated camelCase format. + */ + public Map<String, Object> getLocationProperties(String provider, String namedLocation, Map<String, ?> properties) { + ConfigBag result = ConfigBag.newInstance(); + + if (!Strings.isNullOrEmpty(provider)) + result.put(LocationConfigKeys.CLOUD_PROVIDER, provider); + // named properties are preferred over providerOrApi properties + result.putAll(transformDeprecated(getGenericLocationSingleWordProperties(properties))); + if (!Strings.isNullOrEmpty(provider)) result.putAll(transformDeprecated(getScopedLocationProperties(provider, properties))); + if (!Strings.isNullOrEmpty(namedLocation)) result.putAll(transformDeprecated(getNamedLocationProperties(namedLocation, properties))); + + setLocalTempDir(properties, result); + + return result.getAllConfigRaw(); + } + + /** allow the temp dir where ssh temporary files on the brooklyn server side are placed */ + public static void setLocalTempDir(Map<String,?> source, ConfigBag target) { + // TODO better would be to use BrooklynServerConfig, requiring management passed in + String brooklynDataDir = (String) source.get(BrooklynServerConfig.getMgmtBaseDir(source)); + if (brooklynDataDir != null && brooklynDataDir.length() > 0) { + String tempDir = Os.mergePaths(brooklynDataDir, "tmp", "ssh"); + target.putIfAbsentAndNotNull(SshTool.PROP_LOCAL_TEMP_DIR, tempDir); + Os.deleteOnExitEmptyParentsUpTo(new File(tempDir), new File(brooklynDataDir)); + } + } + + /** + * Gets the named provider (e.g. if using a property like {@code brooklyn.location.named.myfavourite=localhost}, then + * {@code getNamedProvider("myfavourite", properties)} will return {@code "localhost"}). + */ + protected String getNamedProvider(String namedLocation, Map<String, ?> properties) { + String key = String.format("brooklyn.location.named.%s", namedLocation); + return (String) properties.get(key); + } + + /** + * Returns those properties in the form "brooklyn.location.xyz", where "xyz" is any + * key that does not contain dots. We do this special (sub-optimal!) filtering + * because we want to exclude brooklyn.location.named.*, brooklyn.location.jclouds.*, etc. + * We only want those properties that are to be generic for all locations. + * + * Strips off the prefix in the returned map. + */ + protected Map<String, Object> getGenericLocationSingleWordProperties(Map<String, ?> properties) { + return getMatchingSingleWordProperties("brooklyn.location.", properties); + } + + /** + * Gets all properties that start with {@code "brooklyn.location."+scopeSuffix+"."}, stripping off + * the prefix in the returned map. + */ + protected Map<String, Object> getScopedLocationProperties(String scopeSuffix, Map<String, ?> properties) { + checkArgument(!scopeSuffix.startsWith("."), "scopeSuffix \"%s\" should not start with \".\"", scopeSuffix); + checkArgument(!scopeSuffix.endsWith("."), "scopeSuffix \"%s\" should not end with \".\"", scopeSuffix); + String prefix = String.format("brooklyn.location.%s.", scopeSuffix); + return ConfigUtils.filterForPrefixAndStrip(properties, prefix).asMapWithStringKeys(); + } + + /** + * Gets all properties that start with the given {@code fullPrefix}, stripping off + * the prefix in the returned map. + */ + protected Map<String, Object> getMatchingProperties(String fullPrefix, Map<String, ?> properties) { + return ConfigUtils.filterForPrefixAndStrip(properties, fullPrefix).asMapWithStringKeys(); + } + + /** + * Gets all properties that start with either of the given prefixes. The {@code fullPreferredPrefix} + * properties will override any duplicates in {@code fullDeprecatedPrefix}. If there are any + * properties that match the {@code fullDeprecatedPrefix}, then a warning will be logged. + * + * @see #getMatchingProperties(String, Map) + */ + protected Map<String, Object> getMatchingProperties(String fullPreferredPrefix, String fullDeprecatedPrefix, Map<String, ?> properties) { + Map<String, Object> deprecatedResults = getMatchingProperties(fullDeprecatedPrefix, properties); + Map<String, Object> results = getMatchingProperties(fullPreferredPrefix, properties); + + if (deprecatedResults.size() > 0) { + LOG.warn("Deprecated use of properties prefix "+fullDeprecatedPrefix+"; instead use "+fullPreferredPrefix); + return MutableMap.<String, Object>builder() + .putAll(deprecatedResults) + .putAll(results) + .build(); + } else { + return results; + } + } + + /** + * Gets all properties that start with the given {@code fullPrefix}, stripping off + * the prefix in the returned map. + * + * Returns only those properties whose key suffix is a single word (i.e. contains no dots). + * We do this special (sub-optimal!) filtering because we want sub-scoped things + * (e.g. could want brooklyn.location.privateKeyFile, but not brooklyn.location.named.*). + */ + protected Map<String, Object> getMatchingSingleWordProperties(String fullPrefix, Map<String, ?> properties) { + BrooklynProperties filteredProperties = ConfigUtils.filterForPrefixAndStrip(properties, fullPrefix); + return ConfigUtils.filterFor(filteredProperties, Predicates.not(Predicates.containsPattern("\\."))).asMapWithStringKeys(); + } + + /** + * Gets all single-word properties that start with either of the given prefixes. The {@code fullPreferredPrefix} + * properties will override any duplicates in {@code fullDeprecatedPrefix}. If there are any + * properties that match the {@code fullDeprecatedPrefix}, then a warning will be logged. + * + * @see #getMatchingSingleWordProperties(String, Map) + */ + protected Map<String, Object> getMatchingSingleWordProperties(String fullPreferredPrefix, String fullDeprecatedPrefix, Map<String, ?> properties) { + Map<String, Object> deprecatedResults = getMatchingSingleWordProperties(fullDeprecatedPrefix, properties); + Map<String, Object> results = getMatchingSingleWordProperties(fullPreferredPrefix, properties); + + if (deprecatedResults.size() > 0) { + LOG.warn("Deprecated use of properties prefix "+fullDeprecatedPrefix+"; instead use "+fullPreferredPrefix); + return MutableMap.<String, Object>builder() + .putAll(deprecatedResults) + .putAll(results) + .build(); + } else { + return results; + } + } + + protected Map<String, Object> getNamedLocationProperties(String locationName, Map<String, ?> properties) { + checkArgument(!Strings.isNullOrEmpty(locationName), "locationName should not be blank"); + String prefix = String.format("brooklyn.location.named.%s.", locationName); + return ConfigUtils.filterForPrefixAndStrip(properties, prefix).asMapWithStringKeys(); + } + + protected Map<String, Object> transformDeprecated(Map<String, ?> properties) { + Map<String,Object> result = Maps.newLinkedHashMap(); + Map<String, String> deprecatedKeysMapping = getDeprecatedKeysMapping(); + + for (Map.Entry<String,?> entry : properties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (deprecatedKeysMapping.containsKey(key)) { + String transformedKey = deprecatedKeysMapping.get(key); + LOG.warn("Deprecated key {}, transformed to {}; will not be supported in future versions", new Object[] {key, transformedKey}); + result.put(transformedKey, value); + } else { + result.put(key, value); + } + } + + return result; + } + + protected Map<String,String> getDeprecatedKeysMapping() { + return DEPRECATED_KEYS_MAPPING; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationTypeSnapshot.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationTypeSnapshot.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationTypeSnapshot.java new file mode 100644 index 0000000..4dec614 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationTypeSnapshot.java @@ -0,0 +1,41 @@ +/* + * 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.brooklyn.location.basic; + +import java.util.Map; + +import org.apache.brooklyn.policy.EnricherType; + +import brooklyn.basic.BrooklynTypeSnapshot; +import brooklyn.config.ConfigKey; + +public class LocationTypeSnapshot extends BrooklynTypeSnapshot implements EnricherType { + + private static final long serialVersionUID = 9150132836104748237L; + + LocationTypeSnapshot(String name, Map<String, ConfigKey<?>> configKeys) { + super(name, configKeys); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + return (obj instanceof LocationTypeSnapshot) && super.equals(obj); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/Locations.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/Locations.java b/core/src/main/java/org/apache/brooklyn/location/basic/Locations.java new file mode 100644 index 0000000..31855ed --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/Locations.java @@ -0,0 +1,159 @@ +/* + * 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.brooklyn.location.basic; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.management.LocationManager; +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.Entities; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.MachineLocation; +import brooklyn.util.collections.MutableList; +import brooklyn.util.guava.Maybe; +import brooklyn.util.yaml.Yamls; + +import com.google.common.collect.ImmutableList; + +public class Locations { + + private static final Logger log = LoggerFactory.getLogger(Locations.class); + + public static final LocationsFilter USE_FIRST_LOCATION = new LocationsFilter() { + private static final long serialVersionUID = 3100091615409115890L; + + @Override + public List<Location> filterForContext(List<Location> locations, Object context) { + if (locations.size()<=1) return locations; + return ImmutableList.of(locations.get(0)); + } + }; + + public interface LocationsFilter extends Serializable { + public List<Location> filterForContext(List<Location> locations, Object context); + } + + /** as {@link Machines#findUniqueMachineLocation(Iterable)} */ + public static Maybe<MachineLocation> findUniqueMachineLocation(Iterable<? extends Location> locations) { + return Machines.findUniqueMachineLocation(locations); + } + + /** as {@link Machines#findUniqueSshMachineLocation(Iterable)} */ + public static Maybe<SshMachineLocation> findUniqueSshMachineLocation(Iterable<? extends Location> locations) { + return Machines.findUniqueSshMachineLocation(locations); + } + + /** if no locations are supplied, returns locations on the entity, or in the ancestors, until it finds a non-empty set, + * or ultimately the empty set if no locations are anywhere */ + public static Collection<? extends Location> getLocationsCheckingAncestors(Collection<? extends Location> locations, Entity entity) { + // look in ancestors if location not set here + Entity ancestor = entity; + while ((locations==null || locations.isEmpty()) && ancestor!=null) { + locations = ancestor.getLocations(); + ancestor = ancestor.getParent(); + } + return locations; + } + + public static boolean isManaged(Location loc) { + ManagementContext mgmt = ((LocationInternal)loc).getManagementContext(); + return (mgmt != null) && mgmt.isRunning() && mgmt.getLocationManager().isManaged(loc); + } + + public static void unmanage(Location loc) { + if (isManaged(loc)) { + ManagementContext mgmt = ((LocationInternal)loc).getManagementContext(); + mgmt.getLocationManager().unmanage(loc); + } + } + + /** + * Registers the given location (and all its children) with the management context. + * @throws IllegalStateException if the parent location is not already managed + * + * @since 0.6.0 (added only for backwards compatibility, where locations are being created directly; previously in {@link Entities}). + * @deprecated in 0.6.0; use {@link LocationManager#createLocation(LocationSpec)} instead. + */ + public static void manage(Location loc, ManagementContext managementContext) { + if (!managementContext.getLocationManager().isManaged(loc)) { + log.warn("Deprecated use of unmanaged location ("+loc+"); will be managed automatically now but not supported in future versions"); + // FIXME this occurs MOST OF THE TIME e.g. including BrooklynLauncher.location(locationString) + // not sure what is the recommend way to convert from locationString to locationSpec, or the API we want to expose; + // deprecating some of the LocationRegistry methods seems sensible? + log.debug("Stack trace for location of: Deprecated use of unmanaged location; will be managed automatically now but not supported in future versions", new Exception("TRACE for: Deprecated use of unmanaged location")); + managementContext.getLocationManager().manage(loc); + } + } + + public static Location coerce(ManagementContext mgmt, Object rawO) { + if (rawO==null) + return null; + if (rawO instanceof Location) + return (Location)rawO; + + Object raw = rawO; + if (raw instanceof String) + raw = Yamls.parseAll((String)raw).iterator().next(); + + String name; + Map<?, ?> flags = null; + if (raw instanceof Map) { + // for yaml, take the key, and merge with locationFlags + Map<?,?> tm = ((Map<?,?>)raw); + if (tm.size()!=1) { + throw new IllegalArgumentException("Location "+rawO+" is invalid; maps must have only one key, being the location spec string"); + } + name = (String) tm.keySet().iterator().next(); + flags = (Map<?, ?>) tm.values().iterator().next(); + + } else if (raw instanceof String) { + name = (String)raw; + + } else { + throw new IllegalArgumentException("Location "+rawO+" is invalid; can only parse strings or maps"); + } + return mgmt.getLocationRegistry().resolve(name, flags); + } + + public static Collection<? extends Location> coerceToCollection(ManagementContext mgmt, Object rawO) { + if (rawO==null) return null; + Object raw = rawO; + if (raw instanceof Collection) { + List<Location> result = MutableList.<Location>of(); + for (Object o: (Collection<?>)raw) + result.add(coerce(mgmt, o)); + return result; + } + if (raw instanceof String) { + raw = Yamls.parseAll((String)raw).iterator().next(); + if (raw instanceof Collection) + return coerceToCollection(mgmt, raw); + } + return Collections.singletonList( coerce(mgmt, raw) ); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/Machines.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/Machines.java b/core/src/main/java/org/apache/brooklyn/location/basic/Machines.java new file mode 100644 index 0000000..621e1e2 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/Machines.java @@ -0,0 +1,188 @@ +/* + * 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.brooklyn.location.basic; + +import java.net.InetAddress; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.location.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Iterables; + +import brooklyn.entity.basic.Attributes; +import org.apache.brooklyn.location.MachineLocation; +import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine; +import brooklyn.util.guava.Maybe; +import brooklyn.util.net.HasNetworkAddresses; + +/** utilities for working with MachineLocations */ +public class Machines { + + private static final Logger log = LoggerFactory.getLogger(Machines.class); + + public static Maybe<String> getSubnetHostname(Location where) { + // TODO Should we look at HasNetworkAddresses? But that's not a hostname. + String hostname = null; + if (where instanceof HasSubnetHostname) { + hostname = ((HasSubnetHostname) where).getSubnetHostname(); + } + if (hostname == null && where instanceof MachineLocation) { + InetAddress addr = ((MachineLocation) where).getAddress(); + if (addr != null) hostname = addr.getHostAddress(); + } + log.debug("computed subnet hostname {} for {}", hostname, where); + // TODO if Maybe.absent(message) appears, could/should use that + // TODO If no machine available, should we throw new IllegalStateException("Cannot find hostname for "+where); + return Maybe.fromNullable(hostname); + } + + public static Maybe<String> getSubnetIp(Location where) { + // TODO Too much duplication between the ip and hostname methods + String result = null; + if (where instanceof HasSubnetHostname) { + result = ((HasSubnetHostname) where).getSubnetIp(); + } + if (where instanceof HasNetworkAddresses) { + Set<String> privateAddrs = ((HasNetworkAddresses) where).getPrivateAddresses(); + if (privateAddrs.size() > 0) { + result = Iterables.get(privateAddrs, 0); + } + } + if (result == null && where instanceof MachineLocation) { + InetAddress addr = ((MachineLocation) where).getAddress(); + if (addr != null) result = addr.getHostAddress(); + } + log.debug("computed subnet host ip {} for {}", result, where); + return Maybe.fromNullable(result); + } + + @SuppressWarnings("unchecked") + public static <T> Maybe<T> findUniqueElement(Iterable<?> items, Class<T> type) { + if (items==null) return null; + Iterator<?> i = items.iterator(); + T result = null; + while (i.hasNext()) { + Object candidate = i.next(); + if (type.isInstance(candidate)) { + if (result==null) result = (T)candidate; + else { + if (log.isTraceEnabled()) + log.trace("Multiple instances of "+type+" in "+items+"; ignoring"); + return Maybe.absent(new IllegalStateException("Multiple instances of "+type+" in "+items+"; expected a single one")); + } + } + } + if (result==null) + return Maybe.absent(new IllegalStateException("No instances of "+type+" available (in "+items+")")); + return Maybe.of(result); + } + + public static Maybe<MachineLocation> findUniqueMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, MachineLocation.class); + } + + public static Maybe<SshMachineLocation> findUniqueSshMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, SshMachineLocation.class); + } + + public static Maybe<WinRmMachineLocation> findUniqueWinRmMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, WinRmMachineLocation.class); + } + + public static Maybe<String> findSubnetHostname(Iterable<? extends Location> ll) { + Maybe<MachineLocation> l = findUniqueMachineLocation(ll); + if (!l.isPresent()) { + return Maybe.absent(); +// throw new IllegalStateException("Cannot find hostname for among "+ll); + } + return Machines.getSubnetHostname(l.get()); + } + + public static Maybe<String> findSubnetHostname(Entity entity) { + String sh = entity.getAttribute(Attributes.SUBNET_HOSTNAME); + if (sh!=null) return Maybe.of(sh); + return findSubnetHostname(entity.getLocations()); + } + + public static Maybe<String> findSubnetOrPublicHostname(Entity entity) { + String hn = entity.getAttribute(Attributes.HOSTNAME); + if (hn!=null) { + // attributes already set, see if there was a SUBNET_HOSTNAME set + // note we rely on (public) hostname being set _after_ subnet_hostname, + // to prevent tiny possibility of races resulting in hostname being returned + // becasue subnet is still being looked up -- see MachineLifecycleEffectorTasks + Maybe<String> sn = findSubnetHostname(entity); + if (sn.isPresent()) return sn; + // short-circuit discovery if attributes have been set already + return Maybe.of(hn); + } + + Maybe<MachineLocation> l = findUniqueMachineLocation(entity.getLocations()); + if (!l.isPresent()) return Maybe.absent(); + InetAddress addr = l.get().getAddress(); + if (addr==null) return Maybe.absent(); + return Maybe.fromNullable(addr.getHostName()); + } + + public static Maybe<String> findSubnetOrPrivateIp(Entity entity) { + // see comments in findSubnetOrPrivateHostname + String hn = entity.getAttribute(Attributes.ADDRESS); + if (hn!=null) { + Maybe<String> sn = findSubnetIp(entity); + if (sn.isPresent()) return sn; + return Maybe.of(hn); + } + + Maybe<MachineLocation> l = findUniqueMachineLocation(entity.getLocations()); + if (!l.isPresent()) return Maybe.absent(); + InetAddress addr = l.get().getAddress(); + if (addr==null) return Maybe.absent(); + return Maybe.fromNullable(addr.getHostAddress()); + } + + public static Maybe<String> findSubnetIp(Entity entity) { + String sh = entity.getAttribute(Attributes.SUBNET_ADDRESS); + if (sh!=null) return Maybe.of(sh); + return findSubnetIp(entity.getLocations()); + } + + public static Maybe<String> findSubnetIp(Iterable<? extends Location> ll) { + // TODO Or if can't find MachineLocation, should we throw new IllegalStateException("Cannot find hostname for among "+ll); + Maybe<MachineLocation> l = findUniqueMachineLocation(ll); + return (l.isPresent()) ? Machines.getSubnetIp(l.get()) : Maybe.<String>absent(); + } + + /** returns whether it is localhost (and has warned) */ + public static boolean warnIfLocalhost(Collection<? extends Location> locations, String message) { + if (locations.size()==1) { + Location l = locations.iterator().next(); + if (l instanceof LocalhostMachineProvisioningLocation || l instanceof LocalhostMachine) { + log.warn(message); + return true; + } + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocation.java b/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocation.java new file mode 100644 index 0000000..bbf45f0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocation.java @@ -0,0 +1,167 @@ +/* + * 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.brooklyn.location.basic; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.NoMachinesAvailableException; +import org.apache.brooklyn.location.cloud.AbstractAvailabilityZoneExtension; +import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import org.apache.brooklyn.location.MachineLocation; +import org.apache.brooklyn.location.MachineProvisioningLocation; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.CompoundRuntimeException; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.text.Strings; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; + +/** A location which consists of multiple locations stitched together to form availability zones. + * The first location will be used by default, but if an {@link AvailabilityZoneExtension}-aware entity + * is used, it may stripe across each of the locations. See notes at {@link AvailabilityZoneExtension}. */ +public class MultiLocation<T extends MachineLocation> extends AbstractLocation implements MachineProvisioningLocation<T> { + + private static final long serialVersionUID = 7993091317970457862L; + + @SuppressWarnings("serial") + @SetFromFlag("subLocations") + public static final ConfigKey<List<MachineProvisioningLocation<?>>> SUB_LOCATIONS = ConfigKeys.newConfigKey( + new TypeToken<List<MachineProvisioningLocation<?>>>() {}, + "subLocations", + "The sub-machines that this location can delegate to"); + + @Override + public void init() { + super.init(); + List<MachineProvisioningLocation<?>> subLocs = getSubLocations(); + checkState(subLocs.size() >= 1, "sub-locations must not be empty"); + AvailabilityZoneExtension azExtension = new AvailabilityZoneExtensionImpl(getManagementContext(), subLocs); + addExtension(AvailabilityZoneExtension.class, azExtension); + } + + public T obtain() throws NoMachinesAvailableException { + return obtain(MutableMap.of()); + } + + /** finds (creates) and returns a {@link MachineLocation}; + * this always tries the first sub-location, moving on the second and subsequent if the first throws {@link NoMachinesAvailableException}. + * (if you want striping across locations, see notes in {@link AvailabilityZoneExtension}.) */ + @SuppressWarnings("unchecked") + @Override + public T obtain(Map<?, ?> flags) throws NoMachinesAvailableException { + List<MachineProvisioningLocation<?>> sublocsList = getSubLocations(); + Iterator<MachineProvisioningLocation<?>> sublocs = sublocsList.iterator(); + List<NoMachinesAvailableException> errors = MutableList.of(); + while (sublocs.hasNext()) { + try { + return (T) sublocs.next().obtain(flags); + } catch (NoMachinesAvailableException e) { + errors.add(e); + } + } + Exception wrapped; + String msg; + if (errors.size()>1) { + wrapped = new CompoundRuntimeException(errors.size()+" sublocation exceptions, including: "+ + Exceptions.collapseText(errors.get(0)), errors); + msg = Exceptions.collapseText(wrapped); + } else if (errors.size()==1) { + wrapped = errors.get(0); + msg = wrapped.getMessage(); + if (Strings.isBlank(msg)) msg = wrapped.toString(); + } else { + msg = "no sub-locations set for this multi-location"; + wrapped = null; + } + throw new NoMachinesAvailableException("No machines available in any of the "+sublocsList.size()+" location"+Strings.s(sublocsList.size())+ + " configured here: "+msg, wrapped); + } + + public List<MachineProvisioningLocation<?>> getSubLocations() { + return getRequiredConfig(SUB_LOCATIONS); + } + + @SuppressWarnings("unchecked") + @Override + public MachineProvisioningLocation<T> newSubLocation(Map<?, ?> newFlags) { + // TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places) + return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass()) + .parent(this) + .configure(config().getLocalBag().getAllConfig()) // FIXME Should this just be inherited? + .configure(newFlags)); + } + + @SuppressWarnings("unchecked") + @Override + public void release(T machine) { + ((MachineProvisioningLocation<T>)machine.getParent()).release(machine); + } + + @Override + public Map<String, Object> getProvisioningFlags(Collection<String> tags) { + return Maps.<String,Object>newLinkedHashMap(); + } + + @SuppressWarnings("unchecked") + protected MachineProvisioningLocation<T> firstSubLoc() { + return (MachineProvisioningLocation<T>) Iterables.get(getSubLocations(), 0); + } + + protected <K> K getRequiredConfig(ConfigKey<K> key) { + return checkNotNull(getConfig(key), key.getName()); + } + + public static class AvailabilityZoneExtensionImpl extends AbstractAvailabilityZoneExtension implements AvailabilityZoneExtension { + + private final List<MachineProvisioningLocation<?>> subLocations; + + public AvailabilityZoneExtensionImpl(ManagementContext managementContext, List<MachineProvisioningLocation<?>> subLocations) { + super(managementContext); + this.subLocations = ImmutableList.copyOf(subLocations); + } + + @Override + protected List<Location> doGetAllSubLocations() { + return ImmutableList.<Location>copyOf(subLocations); + } + + @Override + protected boolean isNameMatch(Location loc, Predicate<? super String> namePredicate) { + return namePredicate.apply(loc.getDisplayName()); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocationResolver.java new file mode 100644 index 0000000..ac4b7b3 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/MultiLocationResolver.java @@ -0,0 +1,146 @@ +/* + * 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.brooklyn.location.basic; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; +import org.apache.brooklyn.location.LocationSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.text.KeyValueParser; +import brooklyn.util.text.StringEscapes.JavaStringEscapes; + +import com.google.common.collect.Lists; + +public class MultiLocationResolver implements LocationResolver { + + private static final Logger LOG = LoggerFactory.getLogger(MultiLocationResolver.class); + + private static final String MULTI = "multi"; + + private static final Pattern PATTERN = Pattern.compile("(" + MULTI + "|" + MULTI.toUpperCase() + ")" + ":" + "\\((.*)\\)$"); + + private volatile ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + // FIXME pass all flags into the location + + Map globalProperties = registry.getProperties(); + Map<String,?> locationArgs; + if (spec.equalsIgnoreCase(MULTI)) { + locationArgs = MutableMap.copyOf(locationFlags); + } else { + Matcher matcher = PATTERN.matcher(spec); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid location '" + spec + "'; must specify something like multi(targets=named:foo)"); + } + String args = matcher.group(2); + // TODO we are ignoring locationFlags after this (apart from named), looking only at these args + locationArgs = KeyValueParser.parseMap(args); + } + String namedLocation = (String) locationFlags.get("named"); + + Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(null, namedLocation, globalProperties); + MutableMap<String, Object> flags = MutableMap.<String, Object>builder() + .putAll(filteredProperties) + .putAll(locationFlags) + .removeAll("named") + .putAll(locationArgs).build(); + + if (locationArgs.get("targets") == null) { + throw new IllegalArgumentException("target must be specified in single-machine spec"); + } + + // TODO do we need to pass location flags etc into the children to ensure they are inherited? + List<Location> targets = Lists.newArrayList(); + Object targetSpecs = locationArgs.remove("targets"); + try { + if (targetSpecs instanceof String) { + for (String targetSpec : JavaStringEscapes.unwrapJsonishListIfPossible((String)targetSpecs)) { + targets.add(managementContext.getLocationRegistry().resolve(targetSpec)); + } + } else if (targetSpecs instanceof Iterable) { + for (Object targetSpec: (Iterable<?>)targetSpecs) { + if (targetSpec instanceof String) { + targets.add(managementContext.getLocationRegistry().resolve((String)targetSpec)); + } else { + Set<?> keys = ((Map<?,?>)targetSpec).keySet(); + if (keys.size()!=1) + throw new IllegalArgumentException("targets supplied to MultiLocation must be a list of single-entry maps (got map of size "+keys.size()+": "+targetSpec+")"); + Object key = keys.iterator().next(); + Object flagsS = ((Map<?,?>)targetSpec).get(key); + targets.add(managementContext.getLocationRegistry().resolve((String)key, (Map<?,?>)flagsS)); + } + } + } else throw new IllegalArgumentException("targets must be supplied to MultiLocation, either as string spec or list of single-entry maps each being a location spec"); + + MultiLocation result = managementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class) + .configure(flags) + .configure("subLocations", targets) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + + // TODO Important workaround for BasicLocationRegistry.resolveForPeeking. + // That creates a location (from the resolver) and immediately unmanages it. + // The unmanage *must* remove all the targets created here; otherwise we have a leak. + // Adding the targets as children achieves this. + for (Location target : targets) { + target.setParent(result); + } + return result; + + } catch (Exception e) { + // Must clean up after ourselves: don't leak sub-locations on error + if (LOG.isDebugEnabled()) LOG.debug("Problem resolving MultiLocation; cleaning up any sub-locations and rethrowing: "+e); + for (Location target : targets) { + Locations.unmanage(target); + } + throw Exceptions.propagate(e); + } + } + + @Override + public String getPrefix() { + return MULTI; + } + + @Override + public boolean accepts(String spec, LocationRegistry registry) { + return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/NamedLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/NamedLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/NamedLocationResolver.java new file mode 100644 index 0000000..048c28c --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/NamedLocationResolver.java @@ -0,0 +1,97 @@ +/* + * 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.brooklyn.location.basic; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationDefinition; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.config.ConfigBag; +import brooklyn.util.text.Strings; + +/** + * Allows you to say, in your brooklyn.properties: + * + * brooklyn.location.named.foo=localhost + * brooklyn.location.named.foo.user=bob + * brooklyn.location.named.foo.privateKeyFile=~/.ssh/custom-key-for-bob + * brooklyn.location.named.foo.privateKeyPassphrase=WithAPassphrase + * <p> + * or + * <p> + * brooklyn.location.named.bob-aws-east=jclouds:aws-ec2:us-east-1 + * brooklyn.location.named.bob-aws-east.identity=BobId + * brooklyn.location.named.bob-aws-east.credential=BobCred + * <p> + * then you can simply refer to: foo or named:foo (or bob-aws-east or named:bob-aws-east) in any location spec + */ +public class NamedLocationResolver implements LocationResolver { + + public static final Logger log = LoggerFactory.getLogger(NamedLocationResolver.class); + + public static final String NAMED = "named"; + + @SuppressWarnings("unused") + private ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + @SuppressWarnings({ "rawtypes" }) + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + String name = spec; + ConfigBag lfBag = ConfigBag.newInstance(locationFlags).putIfAbsent(LocationInternal.ORIGINAL_SPEC, name); + name = Strings.removeFromStart(spec, getPrefix()+":"); + if (name.toLowerCase().startsWith(NAMED+":")) { + // since 0.7.0 + log.warn("Deprecated use of 'named:' prefix with wrong case ("+spec+"); support may be removed in future versions"); + name = spec.substring( (NAMED+":").length() ); + } + + LocationDefinition ld = registry.getDefinedLocationByName(name); + if (ld==null) throw new NoSuchElementException("No named location defined matching '"+name+"'"); + return ((BasicLocationRegistry)registry).resolveLocationDefinition(ld, lfBag.getAllConfig(), name); + } + + @Override + public String getPrefix() { + return NAMED; + } + + /** accepts anything starting named:xxx or xxx where xxx is a defined location name */ + @Override + public boolean accepts(String spec, LocationRegistry registry) { + if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true; + if (registry.getDefinedLocationByName(spec)!=null) return true; + return false; + } + +}
