/*
* 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.core.location;
import static com.google.common.base.Preconditions.checkArgument;
import java.io.File;
import java.util.Map;
import org.apache.brooklyn.core.config.ConfigUtils;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.internal.ssh.SshTool;
import org.apache.brooklyn.util.os.Os;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
}
}