From f0de70d656972827800adf36fa495888b0a9ee26 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 29 Aug 2016 15:25:44 -0700 Subject: [PATCH] SGF-512 - Configure GemFire System Properties with annotations. (cherry picked from commit 3f078cbb95626ce893c79e4075daaa5fee610507) Signed-off-by: John Blum --- .../config/annotation/EnableManager.java | 83 +--- .../GemFirePropertiesConfiguration.java | 178 +++++++ .../annotation/ManagerConfiguration.java | 32 +- .../annotation/WithGemFireProperties.java | 437 ++++++++++++++++++ .../EmbeddedServiceConfigurationSupport.java | 15 +- .../data/gemfire/util/PropertiesBuilder.java | 143 +++--- .../gemfire/util/PropertiesBuilderTests.java | 155 ++++--- 7 files changed, 838 insertions(+), 205 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/GemFirePropertiesConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/WithGemFireProperties.java diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableManager.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableManager.java index 33d82185..07458bc2 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableManager.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableManager.java @@ -28,11 +28,13 @@ import org.springframework.context.annotation.Import; /** * The EnableManager annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} - * annotated class to embed and start a GemFire Manager service in the GemFire server/data node. + * annotated class to configure, embed and start a GemFire/Geode Manager service in the GemFire/Geode Server. + * + * Automatically sets {@literal jmx-manager} to {@literal true}. * * @author John Blum * @see org.springframework.context.annotation.Import - * @see ManagerConfiguration + * @see org.springframework.data.gemfire.config.annotation.ManagerConfiguration * @since 1.9.0 */ @Target(ElementType.TYPE) @@ -43,15 +45,6 @@ import org.springframework.context.annotation.Import; @SuppressWarnings("unused") public @interface EnableManager { - /** - * If {@literal true} then this member is willing to be a JMX Manager. All the other JMX Manager properties will be - * used when it does become a manager. If this property is {@literal false} then all other jmx-manager-* properties - * are ignored. - * - * Defaults to {@literal true}. - */ - boolean jmxManager() default true; - /** * By default, the JMX Manager will allow full access to all mbeans by any client. If this property is set to * the name of a file then it can restrict clients to only being able to read MBeans; they will not be able @@ -59,17 +52,19 @@ public @interface EnableManager { * in the password file. For more information about the format of this file see Oracle's documentation * of the {@code com.sun.management.jmxremote.access.file} System property. Ignored if {@literal jmx-manager} * is false or if {@literal jmx-manager-port} is zero. + * + * Defaults to unset. */ - String jmxManagerAccessFile() default ""; + String accessFile() default ""; /** * By default, the JMX Manager (when configured with a port) will listen on all the local host's addresses. * You can use this property to configure what IP address or host name the JMX Manager will listen on for * non-HTTP connections. Ignored if JMX Manager is {@literal false} or {@literal jmx-manager-port} is zero. * - * Defaults to {@literal localhost}. + * Defaults to unset. */ - String jmxManagerBindAddress() default ""; + String bindAddress() default ""; /** * Lets you control what hostname will be given to clients that ask the Locator for the location of a JMX Manager. @@ -77,9 +72,9 @@ public @interface EnableManager { * this property allows you to configure a different hostname that will be given to clients. Ignored if * {@literal jmx-manager} is {@literal false} or {@literal jmx-manager-port} is zero. * - * Defaults to {@literal localhost}. + * Defaults to unset. */ - String jmxManagerHostnameForClients() default ""; + String hostnameForClients() default ""; /** * By default, the JMX Manager will allow clients without credentials to connect. If this property is set to @@ -87,8 +82,10 @@ public @interface EnableManager { * be allowed. Most JVMs require that the file is only readable by the owner. For more information about the * format of this file see Oracle's documentation of the {@literal com.sun.management.jmxremote.password.file} * System property. Ignored if {@literal jmx-manager} is {@literal false} or {@literal jmx-manager-port} is zero. + * + * Defaults to unset. */ - String jmxManagerPasswordFile() default ""; + String passwordFile() default ""; /** * The port this JMX Manager will listen to for client connections. If this property is set to zero then GemFire @@ -97,61 +94,25 @@ public @interface EnableManager { * * Defaults to {@literal 1099}. */ - int jmxManagerPort() default ManagerConfiguration.DEFAULT_JMX_MANAGER_PORT; + int port() default ManagerConfiguration.DEFAULT_JMX_MANAGER_PORT; /** - * Enables or disables SSL for connections to the JMX Manager. If {@literal true} and {@literal jmx-manager-port} - * is not zero, then the JMX Manager will only accept SSL connections. If this property is not set, then GemFire - * uses the value of {@literal cluster-ssl-enabled} to determine whether JMX connections should use SSL. + * If {@literal true} then this member will start a JMX Manager when it creates a cache. Management tools + * like Gfsh can be configured to connect to the JMX Manager. In most cases you should not set this + * because a JMX Manager will automatically be started when needed on a member that sets {@literal jmx-manager} + * to {@literal true}. Ignored if {@literal jmx-manager} is {@literal false}. * * Defaults to {@literal false}. */ - boolean jmxManagerSslEnabled() default false; - - /** - * A space-separated list of the valid SSL ciphers for JMX Manager connections. A setting of 'any' uses any ciphers - * that are enabled by default in the configured JSSE provider. If this property is not set, then GemFire uses - * the value of {@literal cluster-ssl-ciphers} to determine which SSL ciphers are used for JMX connections. - * - * Defaults to {@literal any}. - */ - String jmxManagerSslCiphers() default "any"; - - /** - * A space-separated list of the valid SSL protocols for JMX Manager connections. A setting of 'any' uses any - * protocol that is enabled by default in the configured JSSE provider. If this property is not set, then GemFire - * uses the value of {@literal cluster-ssl-protocols} to determine which SSL protocols are used for JMX connections. - * - * Defaults to {@literal any}. - */ - String jmxManagerSslProtocols() default "any"; - - /** - * Boolean indicating whether to require authentication for JMX Manager connections. If this property is not set, - * then GemFire uses the value of {@literal cluster-ssl-require-authentication} to determine whether JMX connections - * require authentication. - * - * Defaults to {@literal true}. - */ - boolean jmxManagerSslRequireAuthentication() default true; - - /** - * If true then this member will start a JMX Manager when it creates a cache. Management tools like Gfsh can be - * configured to connect to the JMX Manager. In most cases you should not set this because a JMX Manager will - * automatically be started when needed on a member that sets {@literal jmx-manager} to {@literal true}. Ignored if - * {@literal jmx-manager} is {@literal false}. - * - * Defaults to {@literal false}. - */ - boolean jmxManagerStart() default false; + boolean start() default false; /** * The rate, in milliseconds, at which this member will push updates to any JMX Managers. Currently this value * should be greater than or equal to the {@literal statistic-sample-rate}. Setting this value too high will * cause stale values to be seen by Gfsh and GemFire Pulse. * - * Defaults to {@literal 2000}. + * Defaults to {@literal 2000} milliseconds. */ - int jmxManagerUpdateRate() default 2000; + int updateRate() default 2000; } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/GemFirePropertiesConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/GemFirePropertiesConfiguration.java new file mode 100644 index 00000000..3a3973ca --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/GemFirePropertiesConfiguration.java @@ -0,0 +1,178 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed 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.springframework.data.gemfire.config.annotation; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; +import org.springframework.data.gemfire.util.PropertiesBuilder; + +/** + * The GemFirePropertiesConfiguration class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} + * capable of configuring additional GemFire Properties on the (Spring Boot) application class at runtime + * during startup. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.WithGemFireProperties + * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport + * @since 1.9.0 + */ +public class GemFirePropertiesConfiguration extends EmbeddedServiceConfigurationSupport { + + public static final boolean DEFAULT_CONSERVE_SOCKETS = true; + public static final boolean DEFAULT_DELTA_PROPAGATION = true; + public static final boolean DEFAULT_DISABLE_TCP = false; + public static final boolean DEFAULT_ENABLE_NETWORK_PARTITION_DETECTION = false; + public static final boolean DEFAULT_ENFORCE_UNIQUE_HOST = false; + public static final boolean DEFAULT_LOAD_CLUSTER_CONFIGURATION_FROM_DIRECTORY = false; + public static final boolean DEFAULT_LOCK_MEMORY = false; + public static final boolean DEFAULT_REMOVE_UNRESPONSIVE_CLIENT = false; + + public static final int DEFAULT_ACK_SEVERE_ALERT_THRESHOLD = 0; + public static final int DEFAULT_ACK_WAIT_THRESHOLD = 15; + public static final int DEFAULT_ASYNC_MAX_QUEUE_SIZE = 8; + public static final int DEFAULT_DISTRIBUTED_SYSTEM_ID = -1; + public static final int DEFAULT_SOCKET_BUFFER_SIZE = 32768; + public static final int DEFAULT_TCP_PORT = 0; + public static final int DEFAULT_TOMBSTONE_THRESHOLD = 100000; + public static final int DEFAULT_UDP_FRAGMENT_SIZE = 60000; + public static final int DEFAULT_UDP_RECEIVE_BUFFER_SIZE = 1048576; + public static final int DEFAULT_UDP_SEND_BUFFER_SIZE = 65535; + + public static final long DEFAULT_ASYNC_DISTRIBUTION_TIMEOUT = 0L; + public static final long DEFAULT_ASYNC_QUEUE_TIMEOUT = 60000L; + public static final long DEFAULT_LOCATOR_WAIT_TIME = 0L; + public static final long DEFAULT_MAX_WAIT_TIME_RECONNECT = 60000L; + public static final long DEFAULT_MEMBER_TIMEOUT = 5000L; + public static final long DEFAULT_SOCKET_LEASE_TIME = 60000L; + + public static final String DEFAULT_CONFLATE_EVENTS = "server"; + public static final String DEFAULT_DEPLOY_WORKING_DIRECTORY = "."; + public static final String DEFAULT_MEMBERSHIP_PORT_RANGE = "1024-65535"; + + /* (non-Javadoc) */ + @Override + protected Class getAnnotationType() { + return WithGemFireProperties.class; + } + + /* (non-Javadoc) */ + @Override + protected Properties toGemFireProperties(Map annotationAttributes) { + PropertiesBuilder gemfireProperties = new PropertiesBuilder(); + + gemfireProperties.setPropertyIfNotDefault("ack-severe-alert-threshold", + annotationAttributes.get("ackSevereAlertThreshold"), DEFAULT_ACK_SEVERE_ALERT_THRESHOLD); + + gemfireProperties.setPropertyIfNotDefault("ack-wait-threshold", + annotationAttributes.get("ackWaitThreshold"), DEFAULT_ACK_WAIT_THRESHOLD); + + gemfireProperties.setPropertyIfNotDefault("async-distribution-timeout", + annotationAttributes.get("asyncDistributionTimeout"), DEFAULT_ASYNC_DISTRIBUTION_TIMEOUT); + + gemfireProperties.setPropertyIfNotDefault("async-max-queue-size", + annotationAttributes.get("asyncMaxQueueSize"), DEFAULT_ASYNC_MAX_QUEUE_SIZE); + + gemfireProperties.setPropertyIfNotDefault("async-queue-timeout", + annotationAttributes.get("asyncQueueTimeout"), DEFAULT_ASYNC_QUEUE_TIMEOUT); + + gemfireProperties.setProperty("bind-address", annotationAttributes.get("bindAddress")); + + gemfireProperties.setProperty("cache-xml-file", annotationAttributes.get("cacheXmlFile")); + + gemfireProperties.setProperty("cluster-configuration-dir", + annotationAttributes.get("clusterConfigurationDirectory")); + + gemfireProperties.setPropertyIfNotDefault("conflate-events", + annotationAttributes.get("conflateEvents"), DEFAULT_CONFLATE_EVENTS); + + gemfireProperties.setPropertyIfNotDefault("conserve-sockets", + annotationAttributes.get("conserveSockets"), DEFAULT_CONSERVE_SOCKETS); + + gemfireProperties.setPropertyIfNotDefault("delta-propagation", + annotationAttributes.get("deltaPropagation"), DEFAULT_DELTA_PROPAGATION); + + gemfireProperties.setPropertyIfNotDefault("deploy-working-dir", + annotationAttributes.get("deployWorkingDirectory"), DEFAULT_DEPLOY_WORKING_DIRECTORY); + + gemfireProperties.setPropertyIfNotDefault("disable-tcp", + annotationAttributes.get("disableTcp"), DEFAULT_DISABLE_TCP); + + gemfireProperties.setPropertyIfNotDefault("distributed-system-id", + annotationAttributes.get("distributedSystemId"), DEFAULT_DISTRIBUTED_SYSTEM_ID); + + gemfireProperties.setPropertyIfNotDefault("enable-network-partition-detection", + annotationAttributes.get("enableNetworkPartitionDetection"), DEFAULT_ENABLE_NETWORK_PARTITION_DETECTION); + + gemfireProperties.setPropertyIfNotDefault("enforce-unique-host", + annotationAttributes.get("enforceUniqueHost"), DEFAULT_ENFORCE_UNIQUE_HOST); + + gemfireProperties.setProperty("groups", (String[]) annotationAttributes.get("groups")); + + gemfireProperties.setPropertyIfNotDefault("load-cluster-configuration-from-dir", + annotationAttributes.get("loadClusterConfigurationFromDirectory"), + DEFAULT_LOAD_CLUSTER_CONFIGURATION_FROM_DIRECTORY); + + gemfireProperties.setPropertyIfNotDefault("locator-wait-time", + annotationAttributes.get("locatorWaitTimeout"), DEFAULT_LOCATOR_WAIT_TIME); + + gemfireProperties.setPropertyIfNotDefault("lock-memory", + annotationAttributes.get("lockMemory"), DEFAULT_LOCK_MEMORY); + + gemfireProperties.setPropertyIfNotDefault("max-wait-time-reconnect", + annotationAttributes.get("maxWaitTimeReconnect"), DEFAULT_MAX_WAIT_TIME_RECONNECT); + + gemfireProperties.setPropertyIfNotDefault("member-timeout", + annotationAttributes.get("memberTimeout"), DEFAULT_MEMBER_TIMEOUT); + + gemfireProperties.setPropertyIfNotDefault("membership-port-range", + annotationAttributes.get("membershipPortRange"), DEFAULT_MEMBERSHIP_PORT_RANGE); + + gemfireProperties.setProperty("redundancy-zone", annotationAttributes.get("redundancyZone")); + + gemfireProperties.setProperty("remote-locators", annotationAttributes.get("remoteLocators")); + + gemfireProperties.setPropertyIfNotDefault("remove-unresponsive-client", + annotationAttributes.get("removeUnresponsiveClient"), DEFAULT_REMOVE_UNRESPONSIVE_CLIENT); + + gemfireProperties.setPropertyIfNotDefault("socket-buffer-size", + annotationAttributes.get("socketBufferSize"), DEFAULT_SOCKET_BUFFER_SIZE); + + gemfireProperties.setPropertyIfNotDefault("socket-lease-time", + annotationAttributes.get("socketLeaseTime"), DEFAULT_SOCKET_LEASE_TIME); + + gemfireProperties.setPropertyIfNotDefault("tcp-port", annotationAttributes.get("tcpPort"), DEFAULT_TCP_PORT); + + gemfireProperties.setPropertyIfNotDefault("tombstone-gc-threshold", + annotationAttributes.get("tombstoneGcThreshold"), DEFAULT_TOMBSTONE_THRESHOLD); + + gemfireProperties.setPropertyIfNotDefault("udp-fragment-size", + annotationAttributes.get("udpFragmentSize"), DEFAULT_UDP_FRAGMENT_SIZE); + + gemfireProperties.setPropertyIfNotDefault("udp-recv-buffer-size", + annotationAttributes.get("udpReceiveBufferSize"), DEFAULT_UDP_RECEIVE_BUFFER_SIZE); + + gemfireProperties.setPropertyIfNotDefault("udp-send-buffer-size", + annotationAttributes.get("udpSendBufferSize"), DEFAULT_UDP_SEND_BUFFER_SIZE); + + gemfireProperties.setProperty("user-command-packages", annotationAttributes.get("userCommandPackages")); + + return gemfireProperties.build(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java index 15f1508c..32909c92 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java @@ -25,7 +25,7 @@ import org.springframework.data.gemfire.util.PropertiesBuilder; /** * The ManagerConfiguration class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} - * that applies additional GemFire configuration by way of GemFire System properties to configure + * that applies additional GemFire configuration using GemFire System {@link Properties} to configure * an embedded GemFire Manager. * * @author John Blum @@ -46,27 +46,15 @@ public class ManagerConfiguration extends EmbeddedServiceConfigurationSupport { protected Properties toGemFireProperties(Map annotationAttributes) { PropertiesBuilder gemfireProperties = new PropertiesBuilder(); - boolean jmxManager = Boolean.valueOf(String.valueOf(annotationAttributes.get("jmxManager"))); - - if (jmxManager) { - gemfireProperties.setProperty("jmx-manager", Boolean.TRUE.toString()); - gemfireProperties.setProperty("jmx-manager-access-file", annotationAttributes.get("jmxManagerAccessFile")); - gemfireProperties.setProperty("jmx-manager-bind-address", annotationAttributes.get("jmxManagerBindAddress")); - gemfireProperties.setProperty("jmx-manager-hostname-for-clients", annotationAttributes.get("jmxManagerHostnameForClients")); - gemfireProperties.setProperty("jmx-manager-password-file", annotationAttributes.get("jmxManagerPasswordFile")); - gemfireProperties.setProperty("jmx-manager-port", resolvePort((Integer) annotationAttributes.get("jmxManagerPort"), DEFAULT_JMX_MANAGER_PORT)); - gemfireProperties.setProperty("jmx-manager-start", annotationAttributes.get("jmxManagerStart")); - gemfireProperties.setProperty("jmx-manager-update-rate", annotationAttributes.get("jmxManagerUpdateRate")); - - boolean jmxManagerSslEnabled = Boolean.valueOf(String.valueOf(annotationAttributes.get("jmxManagerSslEnabled"))); - - if (jmxManagerSslEnabled) { - gemfireProperties.setProperty("jmx-manager-ssl-enabled", Boolean.TRUE.toString()); - gemfireProperties.setProperty("jmx-manager-ssl-ciphers", annotationAttributes.get("jmxManagerSslCiphers")); - gemfireProperties.setProperty("jmx-manager-ssl-protocols", annotationAttributes.get("jmxManagerSslProtocols")); - gemfireProperties.setProperty("jmx-manager-ssl-require-authentication", annotationAttributes.get("jmxManagerSslRequireAuthentication")); - } - } + gemfireProperties.setProperty("jmx-manager", Boolean.TRUE.toString()); + gemfireProperties.setProperty("jmx-manager-access-file", annotationAttributes.get("accessFile")); + gemfireProperties.setProperty("jmx-manager-bind-address", annotationAttributes.get("bindAddress")); + gemfireProperties.setProperty("jmx-manager-hostname-for-clients", annotationAttributes.get("hostnameForClients")); + gemfireProperties.setProperty("jmx-manager-password-file", annotationAttributes.get("passwordFile")); + gemfireProperties.setProperty("jmx-manager-port", + resolvePort((Integer) annotationAttributes.get("port"), DEFAULT_JMX_MANAGER_PORT)); + gemfireProperties.setProperty("jmx-manager-start", annotationAttributes.get("start")); + gemfireProperties.setProperty("jmx-manager-update-rate", annotationAttributes.get("updateRate")); return gemfireProperties.build(); } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/WithGemFireProperties.java b/src/main/java/org/springframework/data/gemfire/config/annotation/WithGemFireProperties.java new file mode 100644 index 00000000..0e8607bf --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/WithGemFireProperties.java @@ -0,0 +1,437 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed 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.springframework.data.gemfire.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * The WithGemFireProperties annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated class to configure GemFire/Geode System properties at runtime during [Spring Boot] application startup. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.GemFirePropertiesConfiguration + * @see GemFire System Properties + * @see Geode System Properties + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(GemFirePropertiesConfiguration.class) +@SuppressWarnings("unused") +public @interface WithGemFireProperties { + + /** + * Number of seconds the distributed system will wait after the {@literal ack-wait-threshold} for a message + * to be acknowledged before it issues an alert at severe level. A value of zero disables this feature. + * + * Defaults to {@literal 0} seconds. + */ + int ackSevereAlertThreshold() default GemFirePropertiesConfiguration.DEFAULT_ACK_SEVERE_ALERT_THRESHOLD; + + /** + * Number of seconds a distributed message can wait for acknowledgment before it sends an alert to signal + * that something might be wrong with the system member that is unresponsive. + * + * The waiter continues to wait. The alerts are logged in the system member’s log as warnings. + * Valid values are in the range 0…2147483647 + * + * Defaults to {@literal 15} seconds. + */ + int ackWaitThreshold() default GemFirePropertiesConfiguration.DEFAULT_ACK_WAIT_THRESHOLD; + + /** + * The number of milliseconds a process that is publishing to this process should attempt to distribute + * a cache operation before switching over to asynchronous messaging for this process. The switch to + * asynchronous messaging lasts until this process catches up, departs, or some specified limit is reached, + * such as async-queue-timeout or async-max-queue-size. + * + * This setting controls only peer-to-peer communication and does not apply to client/server + * or multi-site communication. + * + * Defaults to {@literal 0} milliseconds. + */ + long asyncDistributionTimeout() default GemFirePropertiesConfiguration.DEFAULT_ASYNC_DISTRIBUTION_TIMEOUT; + + /** + * Affects non-conflated asynchronous queues for members that publish to this member. This is the maximum size + * the queue can reach (in megabytes) before the publisher asks this member to leave the distributed system. + * + * Valid values are in the range 0..1024. + * + * This setting controls only peer-to-peer communication and does not apply to client/server + * or multi-site communication. + * + * Defaults to {@literal 8} MB. + */ + int asyncMaxQueueSize() default GemFirePropertiesConfiguration.DEFAULT_ASYNC_MAX_QUEUE_SIZE; + + /** + * Affects asynchronous queues for members that publish to this member. This is the maximum milliseconds + * the publisher should wait with no distribution to this member before it asks this member to leave + * the distributed system. Used for handling slow receivers. + * + * This setting controls only peer-to-peer communication and does not apply to client/server + * or multi-site communication. + * + * Defaults to {@literal 60000} milliseconds. + */ + long asyncQueueTimeout() default GemFirePropertiesConfiguration.DEFAULT_ASYNC_QUEUE_TIMEOUT; + + /** + * Relevant only for multi-homed hosts - machines with multiple network interface cards (NICs). + * Specifies the adapter card the cache binds to for peer-to-peer (P2P) communication. Also specifies + * the default location for GemFire Servers to listen on, which is used unless overridden by + * the {@literal server-bind-address}. An empty string causes the member to listen on the default card + * for the machine. This is a machine-wide attribute used for system member and client/server communication. + * It has no effect on Locator location, unless the Locator is embedded in a member process. + * + * Specify the IP address, not the hostname, because each network card may not have a unique hostname. + * An empty string (the default) causes the member to listen on the default card for the machine. + * + * Defaults to unset. + */ + String bindAddress() default ""; + + /** + * Declarative initialization file for the member’s cache. + * + * Defaults to unset. + */ + String cacheXmlFile() default ""; + + /** + * This property specifies the directory in which the cluster configuration related disk-store and artifacts + * are stored. This property is only applicable to dedicated Locators that have {@literal enable-cluster-configuration} + * set to {@literal true}. + * + * Defaults to unset. + */ + String clusterConfigurationDirectory() default ""; + + /** + * Used only by clients in a client/server installation. This is a client-side property + * that is passed to the server. Affects subscription queue conflation in this client’s + * servers. Specifies whether to conflate (true setting), not conflate (false), or to + * use the server’s conflation setting (server). + * + * Defaults to {@literal server}. + */ + String conflateEvents() default GemFirePropertiesConfiguration.DEFAULT_CONFLATE_EVENTS; + + /** + * Specifies whether sockets are shared by the system member’s threads. If true, threads share, + * and a minimum number of sockets are used to connect to the distributed system. If false, + * every application thread has its own sockets for distribution purposes. You can override + * this setting for individual threads inside your application. Where possible, it is better to + * set {@literal conserve-sockets} to {@literal true} and enable the use of specific extra sockets + * in the application code if needed. WAN deployments increase the messaging demands on a GemFire system. + * To avoid hangs related to WAN messaging, always set {@literal conserve-sockets} to {@literal false} + * for GemFire members that participate in a WAN deployment. + * + * Defaults to {@literal true}. + */ + boolean conserveSockets() default GemFirePropertiesConfiguration.DEFAULT_CONSERVE_SOCKETS; + + /** + * Specifies whether to distribute the deltas for entry updates, instead of the full values, + * between clients and servers, and between peers. + * + * Default to {@literal true}. + */ + boolean deltaPropagation() default GemFirePropertiesConfiguration.DEFAULT_DELTA_PROPAGATION; + + /** + * Working directory used when deploying JAR application files to distributed system members. + * This directory can be local and unique to the member or a shared resource. + * + * Defaults to current working directory of this GemFire JVM process. + */ + String deployWorkingDirectory() default GemFirePropertiesConfiguration.DEFAULT_DEPLOY_WORKING_DIRECTORY; + + /** + * Boolean indicating whether to disable the use of TCP/IP sockets for inter-cache point-to-point messaging. + * If disabled, the cache uses datagram (UDP) sockets. + * + * Defaults to {@literal false}. + */ + boolean disableTcp() default GemFirePropertiesConfiguration.DEFAULT_DISABLE_TCP; + + /** + * Identifier used to distinguish messages from different distributed systems. + * + * Set this to different values for different systems in a multi-site (WAN) configuration. This is required + * for Portable Data eXchange (PDX) data serialization. This setting must be the same for every member + * in the same distributed system and unique to the distributed system within the WAN installation. + * + * -1 means no setting. Valid values are integers in the range -1…255. + * + * Defaults to {@literal -1}. + */ + int distributedSystemId() default GemFirePropertiesConfiguration.DEFAULT_DISTRIBUTED_SYSTEM_ID; + + /** + * Boolean instructing the system to detect and handle splits in the distributed system, typically caused by + * a partitioning of the network (split brain) where the distributed system is running. We recommend setting + * this property to {@literal true}. You must set this property to the same value across all your + * distributed system members. In addition, you must set this property to {@literal true} if you are using + * persistent Regions and configure your Regions to use {@literal DISTRIBUTED_ACK} or {@literal GLOBAL} scope + * to avoid potential data conflicts. + * + * Defaults to {@literal false}. + */ + boolean enableNetworkPartitionDetection() default GemFirePropertiesConfiguration.DEFAULT_ENABLE_NETWORK_PARTITION_DETECTION; + + /** + * Whether partitioned regions will put redundant copies of the same data in different members + * running on the same physical machine. By default, GemFire tries to put redundant copies on different machines, + * but it will put them on the same machine if no other machines are available. Setting this property + * to {@literal true} prevents this and requires different machines for redundant copies. + * + * Defaults to {@literal false}. + */ + boolean enforceUniqueHost() default GemFirePropertiesConfiguration.DEFAULT_ENFORCE_UNIQUE_HOST; + + /** + * Defines the list of groups that this member belongs to. Use commas to separate group names. + * Note that anything defined by the {@literal roles} gemfire property will also be considered + * a group. + * + * Default to unset. + */ + String[] groups() default {}; + + /** + * Setting this property to {@literal true} causes loading of cluster configuration from the + * {@literal cluster_config} directory in the Locator. This property is only applicable to dedicated Locators + * that have {@literal enable-cluster-configuration} set to {@literal true}. + * + * Defaults to {@literal false}. + */ + boolean loadClusterConfigurationFromDirectory() default GemFirePropertiesConfiguration.DEFAULT_LOAD_CLUSTER_CONFIGURATION_FROM_DIRECTORY; + + /** + * The number of seconds that a member should wait for a Locator to start if a Locator is not available + * when attempting to join the distributed system. Use this setting when you are starting Locators + * and peers all at once. This timeout allows peers to wait for the Locators to finish starting up + * before attempting to join the distributed system. + * + * Defaults to {@literal 0} seconds. + */ + long locatorWaitTimeout() default GemFirePropertiesConfiguration.DEFAULT_LOCATOR_WAIT_TIME; + + /** + * When true, locks heap and off-heap memory into RAM to prevent the operating system from paging the memory + * out to disk. + * + * Defaults to {@literal false}. + */ + boolean lockMemory() default GemFirePropertiesConfiguration.DEFAULT_LOCK_MEMORY; + + /** + * Maximum number of milliseconds to wait for the distributed system to reconnect on each reconnect attempt. + * + * {@link PeerCacheApplication#enableAutoReconnect()} or {@link CacheServerApplication#enableAutoReconnect()} + * must be set to {@literal true} for this property to have any effect. + * + * Defaults to {@literal 60000} milliseconds. + */ + long maxWaitTimeReconnect() default GemFirePropertiesConfiguration.DEFAULT_MAX_WAIT_TIME_RECONNECT; + + /** + * GemFire uses the {@literal member-timeout} server configuration, specified in milliseconds, to detect + * the abnormal termination of members. The configuration setting is used in two ways: + * + * 1) First, it is used during the UDP heartbeat detection process. When a member detects that + * a heartbeat datagram is missing from the member that it is monitoring after the time interval + * of 2 * the value of member-timeout, the detecting member attempts to form a TCP/IP stream-socket connection + * with the monitored member as described in the next case. + * + * 2) The property is then used again during the TCP/IP stream-socket connection. If the suspected process + * does not respond to the are you alive datagram within the time period specified in member-timeout, + * the membership coordinator sends out a new membership view that notes the member’s failure. + * + * Valid values are in the range 1000..600000. + * + * Defaults to {@literal 5000} milliseconds. + */ + long memberTimeout() default GemFirePropertiesConfiguration.DEFAULT_MEMBER_TIMEOUT; + + /** + * The range of ports available for unicast UDP messaging and for TCP failure detection. This is specified + * as two integers separated by a hyphen. Different members can use different ranges. + * + * GemFire randomly chooses at least two unique integers from this range for the member, one for + * UDP unicast messaging and the other for TCP failure detection messaging. If {@literal tcp-port} + * is configured to {@literal 0}, it will also randomly select a port from this range for TCP sockets + * used for peer-to-peer communication only. + * + * Therefore, the specified range must include at least three available port numbers (UDP, FD_SOCK, + * and TCP DirectChannel). + * + * The system uniquely identifies the member using the combined host IP address and UDP port number. + * + * You may want to restrict the range of ports that GemFire uses so the product can run in an environment + * where routers only allow traffic on certain ports. + * + * Defaults to {@literal 1024-65535}. + */ + String membershipPortRange() default GemFirePropertiesConfiguration.DEFAULT_MEMBERSHIP_PORT_RANGE; + + /** + * Defines this member’s redundancy zone. Used to separate member’s into different groups for satisfying + * Partitioned Region redundancy. If this property is set, GemFire will not put redundant copies of data + * in members with the same redundancy zone setting. + * + * Defaults to unset. + * + * @see Configure High Availability for a Partitioned Region + */ + String redundancyZone() default ""; + + /** + * Used to configure the Locators that a cluster will use in order to connect to a remote site in a multi-site + * (WAN) configuration. + * + * To use Locators in a WAN configuration, you must specify a unique distributed system ID + * ({@literal distributed-system-id}) for the local cluster and remote Locator(s) for the remote clusters + * to which you will connect. + * + * For each remote Locator, provide a host name and/or address (separated by ‘@’, if you use both), + * followed by a port number in brackets. + * + * Examples: + * + *
+	 *     
+	 * remote-locators=address1[port1],address2[port2]
+	 *
+	 * remote-locators=hostName1@address1[port1],hostName2@address2[port2]
+	 *
+	 * remote-locators=hostName1[port1],hostName2[port2]
+	 *     
+	 * 
+ * + * Defaults to unset. + */ + String remoteLocators() default ""; + + /** + * When this property is set to {@literal true}, the primary server drops unresponsive clients + * from all secondaries and itself. Clients are deemed unresponsive when their messaging queues + * become full on the server. While a client’s queue is full, puts that would add to the queue + * block on the server. + * + * Defaults to {@literal false}. + */ + boolean removeUnresponsiveClient() default GemFirePropertiesConfiguration.DEFAULT_REMOVE_UNRESPONSIVE_CLIENT; + + /** + * Receive buffer sizes in bytes of the TCP/IP connections used for data transmission. To minimize the buffer size + * allocation needed for distributing large, serializable messages, the messages are sent in chunks. This setting + * determines the size of the chunks. Larger buffers can handle large messages more quickly, but take up + * more memory. + * + * Defaults to {@literal 32768} bytes. + */ + int socketBufferSize() default GemFirePropertiesConfiguration.DEFAULT_SOCKET_BUFFER_SIZE; + + /** + * Time, in milliseconds, a thread can have exclusive access to a socket it is not actively using. + * A value of zero causes socket leases to never expire. This property is ignored + * if {@literal conserve-sockets} is {@literal true}. + * + * Valid values are in the range 0..600000. + * + * Defaults to {@literal 60000} milliseconds. + */ + long socketLeaseTime() default GemFirePropertiesConfiguration.DEFAULT_SOCKET_LEASE_TIME; + + /** + * The TCP port to listen on for cache communications. If set to zero, the operating system selects + * an available port. Each process on a machine must have its own TCP port. Note that some operating systems + * restrict the range of ports usable by non-privileged users, and using restricted port numbers can cause + * runtime errors in GemFire startup. + * + * Valid values are in the range 0..65535. + * + * Defaults to {@literal 0}. + */ + int tcpPort() default GemFirePropertiesConfiguration.DEFAULT_TCP_PORT; + + /** + * The number of tombstones that can accumulate before the GemFire member triggers garbage collection + * for tombstones. + * + * Defaults to {@literal 100000} tombstones. + * + * @see How Destroy and Clear Operations Are Resolved + */ + int tombstoneGcThreshold() default GemFirePropertiesConfiguration.DEFAULT_TOMBSTONE_THRESHOLD; + + /** + * Maximum fragment size, in bytes, for transmission over UDP unicast or multicast sockets. Smaller messages + * are combined, if possible, for transmission up to the fragment size setting. + * + * Valid values are in the range 1000..60000. + * + * Defaults to {@literal 60000} bytes. + */ + int udpFragmentSize() default GemFirePropertiesConfiguration.DEFAULT_UDP_FRAGMENT_SIZE; + + /** + * The size of the socket buffer used for incoming UDP point-to-point transmissions. If {@literal disable-tcp} + * is {@literal false}, a reduced buffer size of 65535 is used by default. + * + * The default setting of 1048576 is higher than the default OS maximum buffer size on Unix, which should be + * increased to at least 1 megabyte to provide high-volume messaging on Unix systems. + * + * Valid values are in the range 2048.. OS_maximum. + * + * Defaults to {@literal 1048576} bytes. + */ + int udpReceiveBufferSize() default GemFirePropertiesConfiguration.DEFAULT_UDP_RECEIVE_BUFFER_SIZE; + + /** + * The size of the socket buffer used for outgoing UDP point-to-point transmissions. + * + * Valid values are in the range 2048..OS_maximum. + * + * Defaults to {@literal 65535} bytes. + */ + int udpSendBufferSize() default GemFirePropertiesConfiguration.DEFAULT_UDP_SEND_BUFFER_SIZE; + + /** + * A comma separated list of Java packages that contain classes implementing the Spring Shell + * CommandMarker interface. + * + * Matching classes will be loaded when the VM starts and will be available in the GFSH command-line utility. + * + * Defaults to unset. + */ + String userCommandPackages() default ""; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java index 9a882511..97ccf905 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java @@ -73,9 +73,8 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - if (importingClassMetadata.hasAnnotation(getAnnotationTypeName())) { - Map annotationAttributes = - importingClassMetadata.getAnnotationAttributes(getAnnotationTypeName()); + if (isAnnotationPresent(importingClassMetadata)) { + Map annotationAttributes = getAnnotationAttributes(importingClassMetadata); Properties customGemFireProperties = toGemFireProperties(annotationAttributes); @@ -93,11 +92,21 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ protected abstract Properties toGemFireProperties(Map annotationAttributes); + /* (non-Javadoc) */ + protected boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata) { + return importingClassMetadata.hasAnnotation(getAnnotationTypeName()); + } + /* (non-Javadoc) */ protected boolean hasProperties(Properties properties) { return !CollectionUtils.isEmpty(properties); } + /* (non-Javadoc) */ + protected Map getAnnotationAttributes(AnnotationMetadata importingClassMetadata) { + return importingClassMetadata.getAnnotationAttributes(getAnnotationTypeName()); + } + /* (non-Javadoc) */ protected void registerGemFirePropertiesBeanPostProcessor(BeanDefinitionRegistry registry, Properties customGemFireProperties) { diff --git a/src/main/java/org/springframework/data/gemfire/util/PropertiesBuilder.java b/src/main/java/org/springframework/data/gemfire/util/PropertiesBuilder.java index 1196c7f5..1676e6b7 100644 --- a/src/main/java/org/springframework/data/gemfire/util/PropertiesBuilder.java +++ b/src/main/java/org/springframework/data/gemfire/util/PropertiesBuilder.java @@ -24,6 +24,7 @@ import java.util.Properties; import org.springframework.beans.factory.FactoryBean; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -49,6 +50,70 @@ public class PropertiesBuilder implements FactoryBean { return new PropertiesBuilder(); } + /** + * Constructs and initializes a {@link PropertiesBuilder} containing all properties + * from the given {@link InputStream}. + * + * @param in {@link InputStream} source containing properties to use as the defaults for the constructed builder. + * @return a {@link PropertiesBuilder} initialized with properties from the given {@link InputStream}. + * @throws IllegalArgumentException if the {@link InputStream} cannot be read. + * @see java.io.InputStream + * @see java.util.Properties#load(InputStream) + */ + public static PropertiesBuilder from(InputStream in) { + try { + Properties defaults = new Properties(); + defaults.load(in); + return new PropertiesBuilder(defaults); + } + catch (IOException e) { + throw new IllegalArgumentException("Failed to read properties from InputStream", e); + } + } + + /** + * Constructs and initializes a {@link PropertiesBuilder} containing all properties + * from the given {@link Reader}. + * + * @param reader {@link Reader} source containing properties to use as the defaults for the constructed builder. + * @return a {@link PropertiesBuilder} initialized with properties from the given {@link Reader}. + * @throws IllegalArgumentException if the {@link Reader} cannot be read. + * @see java.io.Reader + * @see java.util.Properties#load(Reader) + */ + public static PropertiesBuilder from(Reader reader) { + try { + Properties defaults = new Properties(); + defaults.load(reader); + return new PropertiesBuilder(defaults); + } + catch (IOException e) { + throw new IllegalArgumentException("Failed to read properties from Reader", e); + } + } + + /** + * Constructs and initializes a {@link PropertiesBuilder} containing all properties + * from the given {@link InputStream} in XML format. + * + * @param xml {@link InputStream} source containing properties in XML format to use as defaults + * for the constructed builder. + * @return a {@link PropertiesBuilder} initialized with properties from the given XML {@link InputStream}. + * @throws IllegalArgumentException if the XML {@link InputStream} cannot be read. + * @see java.io.InputStream + * @see java.util.Properties#loadFromXML(InputStream) + */ + public static PropertiesBuilder fromXml(InputStream xml) { + try { + Properties defaults = new Properties(); + defaults.loadFromXML(xml); + return new PropertiesBuilder(defaults); + } + catch (IOException e) { + throw new IllegalArgumentException("Failed to read properties from XML", e); + } + } + /** * Constructs an instance of the {@link PropertiesBuilder} class. */ @@ -60,6 +125,7 @@ public class PropertiesBuilder implements FactoryBean { * Constructs an instance of the {@link PropertiesBuilder} class initialized with the default {@link Properties}. * * @param defaults {@link Properties} used as the defaults. + * @throws NullPointerException if the {@link Properties} reference is {@literal null}. * @see java.util.Properties */ public PropertiesBuilder(Properties defaults) { @@ -72,10 +138,11 @@ public class PropertiesBuilder implements FactoryBean { * {@link PropertiesBuilder} providing the default {@link Properties} for this builder. * * @param builder {@link PropertiesBuilder} providing the default {@link Properties} for this builder. + * @throws NullPointerException if the {@link PropertiesBuilder} reference is {@literal null}. * @see #PropertiesBuilder(Properties) */ public PropertiesBuilder(PropertiesBuilder builder) { - this(builder != null ? builder.build() : null); + this(builder.build()); } /* @@ -130,68 +197,7 @@ public class PropertiesBuilder implements FactoryBean { * @see org.springframework.data.gemfire.util.PropertiesBuilder */ public PropertiesBuilder add(PropertiesBuilder builder) { - if (builder != null) { - add(builder.build()); - } - - return this; - } - - /** - * Adds all properties from the given {@link InputStream} to this builder. - * - * @param in {@link InputStream} source containing properties to add to this builder. - * @return a reference to this {@link PropertiesBuilder}. - * @throws IllegalArgumentException if the {@link InputStream} cannot be read. - * @see java.util.Properties#load(InputStream) - * @see java.io.InputStream - */ - public PropertiesBuilder from(InputStream in) { - try { - this.properties.load(in); - return this; - } - catch (IOException e) { - throw new IllegalArgumentException("Failed to read properties from InputStream", e); - } - } - - /** - * Adds all properties from the given {@link Reader} to this builder. - * - * @param reader {@link Reader} source containing properties to add to this builder. - * @return a reference to this {@link PropertiesBuilder}. - * @throws IllegalArgumentException if the {@link Reader} cannot be read. - * @see java.util.Properties#load(Reader) - * @see java.io.Reader - */ - public PropertiesBuilder from(Reader reader) { - try { - this.properties.load(reader); - return this; - } - catch (IOException e) { - throw new IllegalArgumentException("Failed to read properties from Reader", e); - } - } - - /** - * Adds all properties from the given {@link InputStream} in XML format to this builder. - * - * @param xml {@link InputStream} source containing properties in XML format to add to this builder. - * @return a reference to this {@link PropertiesBuilder}. - * @throws IllegalArgumentException if the XML {@link InputStream} cannot be read. - * @see java.util.Properties#loadFromXML(InputStream) - * @see java.io.InputStream - */ - public PropertiesBuilder fromXml(InputStream xml) { - try { - this.properties.loadFromXML(xml); - return this; - } - catch (IOException e) { - throw new IllegalArgumentException("Failed to read properties from XML", e); - } + return (builder != null ? add(builder.build()) : this); } /** @@ -206,6 +212,11 @@ public class PropertiesBuilder implements FactoryBean { return (value != null ? setProperty(name, value.toString()) : this); } + public PropertiesBuilder setProperty(String name, Object[] values) { + return (!ObjectUtils.isEmpty(values) ? setProperty(name, StringUtils.arrayToCommaDelimitedString(values)) + : this); + } + /** * Sets a property with the given name to the specified {@link String} value. The property is only set * if the value is not {@literal null}, an empty {@link String} or not equal to the {@link String} literal @@ -218,7 +229,7 @@ public class PropertiesBuilder implements FactoryBean { * @see java.util.Properties#setProperty(String, String) */ public PropertiesBuilder setProperty(String name, String value) { - Assert.hasText(name, String.format("Name [%s] must not be specified", name)); + Assert.hasText(name, String.format("Name [%s] must be specified", name)); if (isValuable(value)) { this.properties.setProperty(name, value); @@ -227,6 +238,10 @@ public class PropertiesBuilder implements FactoryBean { return this; } + public PropertiesBuilder setPropertyIfNotDefault(String name, Object value, T defaultValue) { + return (defaultValue == null || !defaultValue.equals(value) ? setProperty(name, value) : this); + } + /** * Determine whether the given {@link String} value is a valid {@link Properties} value. A property value is * considered valid if it is not null, not empty and not equal to (case-insensitive) {@link String} literal diff --git a/src/test/java/org/springframework/data/gemfire/util/PropertiesBuilderTests.java b/src/test/java/org/springframework/data/gemfire/util/PropertiesBuilderTests.java index 7c18b83e..047422ce 100644 --- a/src/test/java/org/springframework/data/gemfire/util/PropertiesBuilderTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/PropertiesBuilderTests.java @@ -39,7 +39,7 @@ import org.junit.Test; * @author John Blum * @see java.util.Properties * @see org.springframework.data.gemfire.util.PropertiesBuilder - * @since 1.0.0 + * @since 1.9.0 */ public class PropertiesBuilderTests { @@ -84,59 +84,10 @@ public class PropertiesBuilderTests { assertThat(properties, is(notNullValue(Properties.class))); assertThat(properties.size(), is(equalTo(1))); + assertThat(properties.containsKey("one"), is(true)); assertThat(properties.getProperty("one"), is(equalTo("1"))); } - @Test - public void propertiesBuilderIsSingleton() { - assertThat(new PropertiesBuilder().isSingleton(), is(true)); - } - - @Test - public void addPropertiesFromPropertiesIsSuccessful() { - PropertiesBuilder builder = PropertiesBuilder.create() - .setProperty("one", "1") - .setProperty("two", "@"); - - Properties sink = builder.build(); - - assertThat(sink, is(notNullValue(Properties.class))); - assertThat(sink.size(), is(2)); - assertThat(sink.getProperty("one"), is(equalTo("1"))); - assertThat(sink.getProperty("two"), is(equalTo("@"))); - assertThat(sink.containsKey("three"), is(false)); - - Properties source = new Properties(); - - source.setProperty("two", "2"); - source.setProperty("three", "3"); - - builder.add(source); - - sink = builder.build(); - - assertThat(sink, is(notNullValue(Properties.class))); - assertThat(sink.size(), is(equalTo(3))); - assertThat(sink, is(not(sameInstance(source)))); - assertThat(sink.getProperty("one"), is(equalTo("1"))); - assertThat(sink.getProperty("two"), is(equalTo("2"))); - assertThat(sink.getProperty("three"), is(equalTo("3"))); - } - - @Test - public void addPropertiesFromPropertiesBuilderIsSuccessful() { - PropertiesBuilder source = PropertiesBuilder.create() - .setProperty("one", "1") - .setProperty("two", "2"); - - Properties properties = new PropertiesBuilder().add(source).build(); - - assertThat(properties, is(notNullValue(Properties.class))); - assertThat(properties.size(), is(equalTo(2))); - assertThat(properties.getProperty("one"), is(equalTo("1"))); - assertThat(properties.getProperty("two"), is(equalTo("2"))); - } - @Test public void fromInputStreamIsSuccessful() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -145,7 +96,7 @@ public class PropertiesBuilderTests { source.store(out, "fromInputStreamIsSuccessfulTest"); - Properties sink = PropertiesBuilder.create().from(new ByteArrayInputStream(out.toByteArray())).build(); + Properties sink = PropertiesBuilder.from(new ByteArrayInputStream(out.toByteArray())).build(); assertThat(sink, is(notNullValue(Properties.class))); assertThat(sink, is(not(sameInstance(source)))); @@ -160,7 +111,7 @@ public class PropertiesBuilderTests { source.store(writer, "fromReaderIsSuccessfulTest"); - Properties sink = PropertiesBuilder.create().from(new StringReader(writer.toString())).build(); + Properties sink = PropertiesBuilder.from(new StringReader(writer.toString())).build(); assertThat(sink, is(notNullValue(Properties.class))); assertThat(sink, is(not(sameInstance(source)))); @@ -175,13 +126,76 @@ public class PropertiesBuilderTests { source.storeToXML(out, "fromXmlInputStreamIsSuccessfulTest"); - Properties sink = PropertiesBuilder.create().fromXml(new ByteArrayInputStream(out.toByteArray())).build(); + Properties sink = PropertiesBuilder.fromXml(new ByteArrayInputStream(out.toByteArray())).build(); assertThat(sink, is(notNullValue(Properties.class))); assertThat(sink, is(not(sameInstance(source)))); assertThat(sink, is(equalTo(source))); } + @Test + @SuppressWarnings("unchecked") + public void propertiesBuilderObjectTypeIsPropertiesClass() { + assertThat((Class) PropertiesBuilder.create().getObjectType(), is(equalTo(Properties.class))); + } + + @Test + public void propertiesBuilderIsSingletonIsTrue() { + assertThat(new PropertiesBuilder().isSingleton(), is(true)); + } + + @Test + public void addPropertiesFromPropertiesIsSuccessful() { + PropertiesBuilder builder = PropertiesBuilder.create() + .setProperty("one", "1") + .setProperty("two", "@"); + + Properties sink = builder.build(); + + assertThat(sink, is(notNullValue(Properties.class))); + assertThat(sink.size(), is(2)); + assertThat(sink.containsKey("one"), is(true)); + assertThat(sink.containsKey("two"), is(true)); + assertThat(sink.containsKey("three"), is(false)); + assertThat(sink.getProperty("one"), is(equalTo("1"))); + assertThat(sink.getProperty("two"), is(equalTo("@"))); + + Properties source = new Properties(); + + source.setProperty("two", "2"); + source.setProperty("three", "3"); + + builder.add(source); + + sink = builder.build(); + + assertThat(sink, is(notNullValue(Properties.class))); + assertThat(sink.size(), is(equalTo(3))); + assertThat(sink, is(not(sameInstance(source)))); + assertThat(sink.containsKey("one"), is(true)); + assertThat(sink.containsKey("two"), is(true)); + assertThat(sink.containsKey("three"), is(true)); + assertThat(sink.getProperty("one"), is(equalTo("1"))); + assertThat(sink.getProperty("two"), is(equalTo("2"))); + assertThat(sink.getProperty("three"), is(equalTo("3"))); + } + + @Test + public void addPropertiesFromPropertiesBuilderIsSuccessful() { + PropertiesBuilder source = PropertiesBuilder.create() + .setProperty("one", "1") + .setProperty("two", "2"); + + Properties properties = PropertiesBuilder.create().add(source).build(); + + assertThat(properties, is(notNullValue(Properties.class))); + assertThat(properties.size(), is(equalTo(2))); + assertThat(properties.containsKey("one"), is(true)); + assertThat(properties.containsKey("two"), is(true)); + assertThat(properties.getProperty("one"), is(equalTo("1"))); + assertThat(properties.getProperty("two"), is(equalTo("2"))); + } + @Test public void setObjectPropertyValuesIsSuccessful() { Properties properties = PropertiesBuilder.create() @@ -201,9 +215,21 @@ public class PropertiesBuilderTests { assertThat(properties.getProperty("string"), is(equalTo("test"))); } + @Test + public void setObjectArrayPropertyValueIsSuccessful() { + Properties properties = PropertiesBuilder.create() + .setProperty("numbers", new Object[] { "one", "two", "three" }) + .build(); + + assertThat(properties, is(notNullValue(Properties.class))); + assertThat(properties.size(), is(equalTo(1))); + assertThat(properties.containsKey("numbers"), is(true)); + assertThat(properties.getProperty("numbers"), is(equalTo("one,two,three"))); + } + @Test public void setPropertyIgnoresNullObjectValue() { - Properties properties = PropertiesBuilder.create().setProperty("object", null).build(); + Properties properties = PropertiesBuilder.create().setProperty("object", (Object) null).build(); assertThat(properties, is(notNullValue(Properties.class))); assertThat(properties.isEmpty(), is(true)); @@ -215,12 +241,29 @@ public class PropertiesBuilderTests { .setProperty("blank", " ") .setProperty("empty", "") .setProperty("null", "null") + .setProperty("nullWithWhiteSpace", " null ") .build(); assertThat(properties, is(notNullValue(Properties.class))); assertThat(properties.isEmpty(), is(true)); } + @Test + public void setPropertyIgnoresEmptyObjectArray() { + Properties properties = PropertiesBuilder.create().setProperty("emptyArray", new Object[0]).build(); + + assertThat(properties, is(notNullValue(Properties.class))); + assertThat(properties.isEmpty(), is(true)); + } + + @Test + public void setPropertyIgnoresNullObjectArray() { + Properties properties = PropertiesBuilder.create().setProperty("nullArray", (Object[]) null).build(); + + assertThat(properties, is(notNullValue(Properties.class))); + assertThat(properties.isEmpty(), is(true)); + } + @Test public void setStringPropertyValuesIsSuccessful() { Properties properties = PropertiesBuilder.create() @@ -230,6 +273,8 @@ public class PropertiesBuilderTests { assertThat(properties, is(notNullValue(Properties.class))); assertThat(properties.size(), is(equalTo(2))); + assertThat(properties.containsKey("one"), is(true)); + assertThat(properties.containsKey("two"), is(true)); assertThat(properties.getProperty("one"), is(equalTo("1"))); assertThat(properties.getProperty("two"), is(equalTo("2"))); }