blob: 8dc718bd38c2fd9a3f0796cbee81fe19acac8044 [file] [log] [blame]
/*
* 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.geode.internal;
import static org.apache.geode.distributed.ConfigurationProperties.MEMBERSHIP_PORT_RANGE;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_SHIRO_INIT;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.apache.geode.InternalGemFireException;
import org.apache.geode.distributed.internal.FlowControlParams;
import org.apache.geode.internal.net.SocketCreator;
import org.apache.geode.internal.security.SecurableCommunicationChannel;
import org.apache.geode.internal.util.ArgumentRedactor;
/**
* Provides an implementation of the {@link Config} interface that implements functionality that all
* {@link Config} implementations can share.
*/
public abstract class AbstractConfig implements Config {
private static final String GEM_FIRE_PROPERTIES_USING_DEFAULT_VALUES =
"### GemFire Properties using default values ###";
private static final String GEM_FIRE_PROPERTIES_DEFINED_WITH_PREFIX =
"### GemFire Properties defined with ";
private static final String GEM_FIRE_PROPERTIES_DEFINED_WITH_SUFFIX = " ###";
/**
* Use {@link #toLoggerString()} instead. If you need to override this in a subclass, be careful
* not to expose any private data or security related values. Fixing bug #48155 by not exposing
* all values.
*/
@Override
public String toString() {
return super.toString();
}
@Override
public String toLoggerString() {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
printSourceSection(ConfigSource.runtime(), printWriter);
printSourceSection(ConfigSource.sysprop(), printWriter);
printSourceSection(ConfigSource.api(), printWriter);
for (ConfigSource fileSource : getFileSources()) {
printSourceSection(fileSource, printWriter);
}
printSourceSection(ConfigSource.xml(), printWriter);
printSourceSection(ConfigSource.launcher(), printWriter); // fix for bug 46653
printSourceSection(null, printWriter);
printWriter.close();
return stringWriter.toString();
}
/**
* Gets the Map of GemFire properties and values from a given ConfigSource
*
* @return map of GemFire properties and values
*/
public Map<String, String> getConfigPropsFromSource(ConfigSource source) {
Map<String, String> configProps = new HashMap<>();
String[] validAttributeNames = getAttributeNames();
Map<String, ConfigSource> sm = getAttSourceMap();
for (String name : validAttributeNames) {
if (source == null && sm.get(name) != null
|| source != null && !source.equals(sm.get(name))) {
continue;
}
configProps.put(name, getAttribute(name));
}
return configProps;
}
/**
* Gets all the GemFire properties defined using file(s)
*
* @return Map of GemFire properties and values set using property files
*/
public Map<String, String> getConfigPropsDefinedUsingFiles() {
Map<String, String> configProps = new HashMap<>();
for (ConfigSource fileSource : getFileSources()) {
configProps.putAll(getConfigPropsFromSource(fileSource));
}
return configProps;
}
@Override
public Properties toProperties() {
Properties result = new SortedProperties();
String[] attNames = getAttributeNames();
for (String name : attNames) {
result.setProperty(name, getAttribute(name));
}
return result;
}
@Override
public void toFile(File file) throws IOException {
try (FileOutputStream out = new FileOutputStream(file)) {
toProperties().store(out, null);
}
}
@Override
public boolean sameAs(Config other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (!getClass().equals(other.getClass())) {
return false;
}
String[] validAttributeNames = getAttributeNames();
for (String name : validAttributeNames) {
Object thisAtt = getAttributeObject(name);
Object otherAtt = other.getAttributeObject(name);
if (thisAtt != otherAtt) {
if (thisAtt == null) {
return false;
} else if (thisAtt.getClass().isArray()) {
int thisLength = Array.getLength(thisAtt);
int otherLength = Array.getLength(otherAtt);
if (thisLength != otherLength) {
return false;
}
for (int j = 0; j < thisLength; j++) {
Object thisArrObj = Array.get(thisAtt, j);
Object otherArrObj = Array.get(otherAtt, j);
if (thisArrObj != otherArrObj) {
if (thisArrObj == null) {
return false;
} else if (!thisArrObj.equals(otherArrObj)) {
return false;
}
}
}
} else if (!thisAtt.equals(otherAtt)) {
return false;
}
}
}
return true;
}
@Override
public String getAttribute(String name) {
Object result = getAttributeObject(name);
if (result instanceof String) {
return (String) result;
}
if (name.equalsIgnoreCase(MEMBERSHIP_PORT_RANGE)) {
int[] value = (int[]) result;
return "" + value[0] + "-" + value[1];
}
if (result.getClass().isArray() && name.startsWith("ssl-")) {
return SystemAdmin.join((Object[]) result, ",");
}
if (result.getClass().isArray()) {
return SystemAdmin.join((Object[]) result);
}
if (result instanceof InetAddress) {
InetAddress addr = (InetAddress) result;
String addrName;
if (addr.isMulticastAddress() || !SocketCreator.resolve_dns) {
// on Windows getHostName on mcast addrs takes ~5 seconds
addrName = addr.getHostAddress();
} else {
addrName = SocketCreator.getHostName(addr);
}
return addrName;
}
return result.toString();
}
@Override
public ConfigSource getAttributeSource(String name) {
return getAttSourceMap().get(name);
}
@Override
public void setAttribute(String name, String value, ConfigSource source) {
Object attObjectValue;
Class valueType = getAttributeType(name);
try {
if (valueType.equals(String.class)) {
attObjectValue = value;
} else if (valueType.equals(String[].class)) {
attObjectValue = value.split(",");
} else if (valueType.equals(Integer.class)) {
attObjectValue = Integer.valueOf(value);
} else if (valueType.equals(Long.class)) {
attObjectValue = Long.valueOf(value);
} else if (valueType.equals(Boolean.class)) {
attObjectValue = Boolean.valueOf(value);
} else if (valueType.equals(File.class)) {
attObjectValue = new File(value);
} else if (valueType.equals(int[].class)) {
int minus = value.indexOf('-');
if (minus <= 0) {
throw new IllegalArgumentException(
"expected a setting in the form X-Y but found no dash for attribute " + name);
}
int[] tempValue = new int[2];
tempValue[0] = Integer.valueOf(value.substring(0, minus));
tempValue[1] = Integer.valueOf(value.substring(minus + 1));
attObjectValue = tempValue;
} else if (valueType.equals(InetAddress.class)) {
try {
attObjectValue = InetAddress.getByName(value);
} catch (UnknownHostException ex) {
throw new IllegalArgumentException(
String.format("%s value %s must be a valid host name. %s",
name, value, ex.toString()));
}
} else if (valueType.equals(FlowControlParams.class)) {
String[] values = value.split(",");
if (values.length != 3) {
throw new IllegalArgumentException(
String.format("%s value %s must have three elements separated by commas",
name, value));
}
int allowance;
float threshold;
int waitTime;
try {
allowance = Integer.parseInt(values[0].trim());
threshold = Float.valueOf(values[1].trim());
waitTime = Integer.parseInt(values[2].trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
String.format("%s value %s must be composed of an integer, a float, and an integer",
name, value));
}
attObjectValue = new FlowControlParams(allowance, threshold, waitTime);
} else if (valueType.isArray()
&& SecurableCommunicationChannel.class.equals(valueType.getComponentType())) {
attObjectValue = commaDelimitedStringToSecurableCommunicationChannels(value);
} else {
throw new InternalGemFireException(
String.format("unhandled attribute type %s for %s.",
valueType, name));
}
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(String.format("%s value %s must be a number.",
name, value));
}
setAttributeObject(name, attObjectValue, source);
}
@Override
public String getAttributeDescription(String name) {
checkAttributeName(name);
if (!getAttDescMap().containsKey(name)) {
throw new InternalGemFireException(
String.format("unhandled attribute name %s.", name));
}
return (String) getAttDescMap().get(name);
}
/**
* Returns the string to use as the exception message when an attempt is made to set an
* unmodifiable attribute.
*/
protected String _getUnmodifiableMsg(String name) {
return String.format("The %s configuration attribute can not be modified.",
name);
}
/**
* Returns a map that contains attribute descriptions
*/
protected abstract Map getAttDescMap();
protected abstract Map<String, ConfigSource> getAttSourceMap();
/**
* Set to true if most of the attributes can be modified. Set to false if most of the attributes
* are read only.
*/
protected boolean _modifiableDefault() {
return false;
}
protected void checkAttributeName(String name) {
String[] validAttNames = getAttributeNames();
if (!Arrays.asList(validAttNames).contains(name.toLowerCase())) {
throw new IllegalArgumentException(
String.format("Unknown configuration attribute name %s. Valid attribute names are: %s .",
name, SystemAdmin.join(validAttNames)));
}
}
private List<ConfigSource> getFileSources() {
ArrayList<ConfigSource> result = new ArrayList<>();
for (ConfigSource cs : getAttSourceMap().values()) {
if (cs.getType() == ConfigSource.Type.FILE || cs.getType() == ConfigSource.Type.SECURE_FILE) {
if (!result.contains(cs)) {
result.add(cs);
}
}
}
return result;
}
private void printSourceSection(ConfigSource source, PrintWriter printWriter) {
String[] validAttributeNames = getAttributeNames();
boolean sourceFound = false;
Map<String, ConfigSource> sourceMap = getAttSourceMap();
boolean sourceIsSecured = false;
if (source != null && source.getType() == ConfigSource.Type.SECURE_FILE) {
sourceIsSecured = true;
}
for (String name : validAttributeNames) {
if (source == null) {
if (sourceMap.get(name) != null) {
continue;
}
} else if (!source.equals(sourceMap.get(name))) {
continue;
}
if (!sourceFound) {
sourceFound = true;
if (source == null) {
printWriter.println(GEM_FIRE_PROPERTIES_USING_DEFAULT_VALUES);
} else {
printWriter.println(GEM_FIRE_PROPERTIES_DEFINED_WITH_PREFIX + source.getDescription()
+ GEM_FIRE_PROPERTIES_DEFINED_WITH_SUFFIX);
}
}
// hide the shiro-init configuration for now. Remove after we can allow customer to specify
// shiro.ini file
if (name.equals(SECURITY_SHIRO_INIT)) {
continue;
}
String attributeValueToPrint;
if (source == null) {
// always show defaults values
attributeValueToPrint = getAttribute(name);
} else if (sourceIsSecured) {
// Never show secure sources
attributeValueToPrint = ArgumentRedactor.redacted;
} else {
// Otherwise, redact based on the key string
attributeValueToPrint =
ArgumentRedactor.redactArgumentIfNecessary(name, getAttribute(name));
}
printWriter.print(name);
printWriter.print('=');
printWriter.println(attributeValueToPrint);
}
}
private SecurableCommunicationChannel[] commaDelimitedStringToSecurableCommunicationChannels(
final String tokenizeString) {
StringTokenizer stringTokenizer = new StringTokenizer(tokenizeString, ",");
SecurableCommunicationChannel[] returnArray =
new SecurableCommunicationChannel[stringTokenizer.countTokens()];
for (int i = 0; i < returnArray.length; i++) {
String name = stringTokenizer.nextToken();
try {
returnArray[i] = SecurableCommunicationChannel.getEnum(name);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
return returnArray;
}
/**
* This class was added to fix bug 39382. It does this be overriding "keys" which is used by the
* store0 implementation of Properties.
*/
protected static class SortedProperties extends Properties {
private static final long serialVersionUID = 7156507110684631135L;
@Override
public Enumeration keys() {
// the TreeSet gets the sorting we desire but is only safe
// because the keys in this context are always String which is Comparable
return Collections.enumeration(new TreeSet(keySet()));
}
}
}