001/*
002 * Copyright 2012-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.env;
018
019import java.util.Random;
020import java.util.UUID;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.core.env.ConfigurableEnvironment;
026import org.springframework.core.env.PropertySource;
027import org.springframework.core.env.StandardEnvironment;
028import org.springframework.util.DigestUtils;
029import org.springframework.util.StringUtils;
030
031/**
032 * {@link PropertySource} that returns a random value for any property that starts with
033 * {@literal "random."}. Where the "unqualified property name" is the portion of the
034 * requested property name beyond the "random." prefix, this {@link PropertySource}
035 * returns:
036 * <ul>
037 * <li>When {@literal "int"}, a random {@link Integer} value, restricted by an optionally
038 * specified range.</li>
039 * <li>When {@literal "long"}, a random {@link Long} value, restricted by an optionally
040 * specified range.</li>
041 * <li>Otherwise, a {@code byte[]}.</li>
042 * </ul>
043 * The {@literal "random.int"} and {@literal "random.long"} properties supports a range
044 * suffix whose syntax is:
045 * <p>
046 * {@code OPEN value (,max) CLOSE} where the {@code OPEN,CLOSE} are any character and
047 * {@code value,max} are integers. If {@code max} is provided then {@code value} is the
048 * minimum value and {@code max} is the maximum (exclusive).
049 *
050 * @author Dave Syer
051 * @author Matt Benson
052 */
053public class RandomValuePropertySource extends PropertySource<Random> {
054
055        /**
056         * Name of the random {@link PropertySource}.
057         */
058        public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";
059
060        private static final String PREFIX = "random.";
061
062        private static final Log logger = LogFactory.getLog(RandomValuePropertySource.class);
063
064        public RandomValuePropertySource(String name) {
065                super(name, new Random());
066        }
067
068        public RandomValuePropertySource() {
069                this(RANDOM_PROPERTY_SOURCE_NAME);
070        }
071
072        @Override
073        public Object getProperty(String name) {
074                if (!name.startsWith(PREFIX)) {
075                        return null;
076                }
077                if (logger.isTraceEnabled()) {
078                        logger.trace("Generating random property for '" + name + "'");
079                }
080                return getRandomValue(name.substring(PREFIX.length()));
081        }
082
083        private Object getRandomValue(String type) {
084                if (type.equals("int")) {
085                        return getSource().nextInt();
086                }
087                if (type.equals("long")) {
088                        return getSource().nextLong();
089                }
090                String range = getRange(type, "int");
091                if (range != null) {
092                        return getNextIntInRange(range);
093                }
094                range = getRange(type, "long");
095                if (range != null) {
096                        return getNextLongInRange(range);
097                }
098                if (type.equals("uuid")) {
099                        return UUID.randomUUID().toString();
100                }
101                return getRandomBytes();
102        }
103
104        private String getRange(String type, String prefix) {
105                if (type.startsWith(prefix)) {
106                        int startIndex = prefix.length() + 1;
107                        if (type.length() > startIndex) {
108                                return type.substring(startIndex, type.length() - 1);
109                        }
110                }
111                return null;
112        }
113
114        private int getNextIntInRange(String range) {
115                String[] tokens = StringUtils.commaDelimitedListToStringArray(range);
116                int start = Integer.parseInt(tokens[0]);
117                if (tokens.length == 1) {
118                        return getSource().nextInt(start);
119                }
120                return start + getSource().nextInt(Integer.parseInt(tokens[1]) - start);
121        }
122
123        private long getNextLongInRange(String range) {
124                String[] tokens = StringUtils.commaDelimitedListToStringArray(range);
125                if (tokens.length == 1) {
126                        return Math.abs(getSource().nextLong() % Long.parseLong(tokens[0]));
127                }
128                long lowerBound = Long.parseLong(tokens[0]);
129                long upperBound = Long.parseLong(tokens[1]) - lowerBound;
130                return lowerBound + Math.abs(getSource().nextLong() % upperBound);
131        }
132
133        private Object getRandomBytes() {
134                byte[] bytes = new byte[32];
135                getSource().nextBytes(bytes);
136                return DigestUtils.md5DigestAsHex(bytes);
137        }
138
139        public static void addToEnvironment(ConfigurableEnvironment environment) {
140                environment.getPropertySources().addAfter(
141                                StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
142                                new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
143                logger.trace("RandomValuePropertySource add to Environment");
144        }
145
146}