001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018package org.apache.logging.log4j.core.config.plugins.convert;
019
020import java.io.File;
021import java.math.BigDecimal;
022import java.math.BigInteger;
023import java.net.InetAddress;
024import java.net.MalformedURLException;
025import java.net.URI;
026import java.net.URISyntaxException;
027import java.net.URL;
028import java.nio.charset.Charset;
029import java.nio.file.Path;
030import java.nio.file.Paths;
031import java.security.Provider;
032import java.security.Security;
033import java.util.UUID;
034import java.util.regex.Pattern;
035
036import org.apache.logging.log4j.Level;
037import org.apache.logging.log4j.Logger;
038import org.apache.logging.log4j.core.appender.rolling.action.Duration;
039import org.apache.logging.log4j.core.config.plugins.Plugin;
040import org.apache.logging.log4j.core.util.CronExpression;
041import org.apache.logging.log4j.status.StatusLogger;
042import org.apache.logging.log4j.util.LoaderUtil;
043
044/**
045 * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find
046 * registered TypeConverters.
047 *
048 * @since 2.1 Moved to the {@code convert} package.
049 */
050public final class TypeConverters {
051
052    /**
053     * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins.
054     *
055     * @since 2.1
056     */
057    public static final String CATEGORY = "TypeConverter";
058
059    /**
060     * Parses a {@link String} into a {@link BigDecimal}.
061     */
062    @Plugin(name = "BigDecimal", category = CATEGORY)
063    public static class BigDecimalConverter implements TypeConverter<BigDecimal> {
064        @Override
065        public BigDecimal convert(final String s) {
066            return new BigDecimal(s);
067        }
068    }
069
070    /**
071     * Parses a {@link String} into a {@link BigInteger}.
072     */
073    @Plugin(name = "BigInteger", category = CATEGORY)
074    public static class BigIntegerConverter implements TypeConverter<BigInteger> {
075        @Override
076        public BigInteger convert(final String s) {
077            return new BigInteger(s);
078        }
079    }
080
081    /**
082     * Converts a {@link String} into a {@link Boolean}.
083     */
084    @Plugin(name = "Boolean", category = CATEGORY)
085    public static class BooleanConverter implements TypeConverter<Boolean> {
086        @Override
087        public Boolean convert(final String s) {
088            return Boolean.valueOf(s);
089        }
090    }
091
092    /**
093     * Converts a {@link String} into a {@code byte[]}.
094     * 
095     * The supported formats are:
096     * <ul>
097     * <li>0x0123456789ABCDEF</li>
098     * <li>Base64:ABase64String</li>
099     * <li>String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]</li>
100     * </ul>
101     */
102    @Plugin(name = "ByteArray", category = CATEGORY)
103    public static class ByteArrayConverter implements TypeConverter<byte[]> {
104
105        private static final String PREFIX_0x = "0x";
106        private static final String PREFIX_BASE64 = "Base64:";
107
108        @Override
109        public byte[] convert(final String value) {
110            byte[] bytes;
111            if (value == null || value.isEmpty()) {
112                bytes = new byte[0];
113            } else if (value.startsWith(PREFIX_BASE64)) {
114                final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length());
115                bytes = Base64Converter.parseBase64Binary(lexicalXSDBase64Binary);
116            } else if (value.startsWith(PREFIX_0x)) {
117                final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length());
118                bytes = HexConverter.parseHexBinary(lexicalXSDHexBinary);
119            } else {
120                bytes = value.getBytes(Charset.defaultCharset());
121            }
122            return bytes;
123        }
124    }
125
126    /**
127     * Converts a {@link String} into a {@link Byte}.
128     */
129    @Plugin(name = "Byte", category = CATEGORY)
130    public static class ByteConverter implements TypeConverter<Byte> {
131        @Override
132        public Byte convert(final String s) {
133            return Byte.valueOf(s);
134        }
135    }
136
137    /**
138     * Converts a {@link String} into a {@link Character}.
139     */
140    @Plugin(name = "Character", category = CATEGORY)
141    public static class CharacterConverter implements TypeConverter<Character> {
142        @Override
143        public Character convert(final String s) {
144            if (s.length() != 1) {
145                throw new IllegalArgumentException("Character string must be of length 1: " + s);
146            }
147            return Character.valueOf(s.toCharArray()[0]);
148        }
149    }
150
151    /**
152     * Converts a {@link String} into a {@code char[]}.
153     */
154    @Plugin(name = "CharacterArray", category = CATEGORY)
155    public static class CharArrayConverter implements TypeConverter<char[]> {
156        @Override
157        public char[] convert(final String s) {
158            return s.toCharArray();
159        }
160    }
161
162    /**
163     * Converts a {@link String} into a {@link Charset}.
164     */
165    @Plugin(name = "Charset", category = CATEGORY)
166    public static class CharsetConverter implements TypeConverter<Charset> {
167        @Override
168        public Charset convert(final String s) {
169            return Charset.forName(s);
170        }
171    }
172
173    /**
174     * Converts a {@link String} into a {@link Class}.
175     */
176    @Plugin(name = "Class", category = CATEGORY)
177    public static class ClassConverter implements TypeConverter<Class<?>> {
178        @Override
179        public Class<?> convert(final String s) throws ClassNotFoundException {
180            switch (s.toLowerCase()) {
181                case "boolean":
182                    return boolean.class;
183                case "byte":
184                    return byte.class;
185                case "char":
186                    return char.class;
187                case "double":
188                    return double.class;
189                case "float":
190                    return float.class;
191                case "int":
192                    return int.class;
193                case "long":
194                    return long.class;
195                case "short":
196                    return short.class;
197                case "void":
198                    return void.class;
199                default:
200                    return LoaderUtil.loadClass(s);
201            }
202
203        }
204    }
205
206    @Plugin(name = "CronExpression", category = CATEGORY)
207    public static class CronExpressionConverter implements TypeConverter<CronExpression> {
208        @Override
209        public CronExpression convert(final String s) throws Exception {
210            return new CronExpression(s);
211        }
212    }
213
214    /**
215     * Converts a {@link String} into a {@link Double}.
216     */
217    @Plugin(name = "Double", category = CATEGORY)
218    public static class DoubleConverter implements TypeConverter<Double> {
219        @Override
220        public Double convert(final String s) {
221            return Double.valueOf(s);
222        }
223    }
224
225    /**
226     * Converts a {@link String} into a {@link Duration}.
227     * @since 2.5
228     */
229    @Plugin(name = "Duration", category = CATEGORY)
230    public static class DurationConverter implements TypeConverter<Duration> {
231        @Override
232        public Duration convert(final String s) {
233            return Duration.parse(s);
234        }
235    }
236
237    /**
238     * Converts a {@link String} into a {@link File}.
239     */
240    @Plugin(name = "File", category = CATEGORY)
241    public static class FileConverter implements TypeConverter<File> {
242        @Override
243        public File convert(final String s) {
244            return new File(s);
245        }
246    }
247
248    /**
249     * Converts a {@link String} into a {@link Float}.
250     */
251    @Plugin(name = "Float", category = CATEGORY)
252    public static class FloatConverter implements TypeConverter<Float> {
253        @Override
254        public Float convert(final String s) {
255            return Float.valueOf(s);
256        }
257    }
258
259    /**
260     * Converts a {@link String} into an {@link InetAddress}.
261     */
262    @Plugin(name = "InetAddress", category = CATEGORY)
263    public static class InetAddressConverter implements TypeConverter<InetAddress> {
264        @Override
265        public InetAddress convert(final String s) throws Exception {
266            return InetAddress.getByName(s);
267        }
268    }
269
270    /**
271     * Converts a {@link String} into a {@link Integer}.
272     */
273    @Plugin(name = "Integer", category = CATEGORY)
274    public static class IntegerConverter implements TypeConverter<Integer> {
275        @Override
276        public Integer convert(final String s) {
277            return Integer.valueOf(s);
278        }
279    }
280
281    /**
282     * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names.
283     */
284    @Plugin(name = "Level", category = CATEGORY)
285    public static class LevelConverter implements TypeConverter<Level> {
286        @Override
287        public Level convert(final String s) {
288            return Level.valueOf(s);
289        }
290    }
291
292    /**
293     * Converts a {@link String} into a {@link Long}.
294     */
295    @Plugin(name = "Long", category = CATEGORY)
296    public static class LongConverter implements TypeConverter<Long> {
297        @Override
298        public Long convert(final String s) {
299            return Long.valueOf(s);
300        }
301    }
302
303    /**
304     * Converts a {@link String} into a {@link Path}.
305     * @since 2.8
306     */
307    @Plugin(name = "Path", category = CATEGORY)
308    public static class PathConverter implements TypeConverter<Path> {
309        @Override
310        public Path convert(final String s) throws Exception {
311            return Paths.get(s);
312        }
313    }
314
315    /**
316     * Converts a {@link String} into a {@link Pattern}.
317     */
318    @Plugin(name = "Pattern", category = CATEGORY)
319    public static class PatternConverter implements TypeConverter<Pattern> {
320        @Override
321        public Pattern convert(final String s) {
322            return Pattern.compile(s);
323        }
324    }
325
326    /**
327     * Converts a {@link String} into a {@link Provider}.
328     */
329    @Plugin(name = "SecurityProvider", category = CATEGORY)
330    public static class SecurityProviderConverter implements TypeConverter<Provider> {
331        @Override
332        public Provider convert(final String s) {
333            return Security.getProvider(s);
334        }
335    }
336
337    /**
338     * Converts a {@link String} into a {@link Short}.
339     */
340    @Plugin(name = "Short", category = CATEGORY)
341    public static class ShortConverter implements TypeConverter<Short> {
342        @Override
343        public Short convert(final String s) {
344            return Short.valueOf(s);
345        }
346    }
347
348    /**
349     * Returns the given {@link String}, no conversion takes place.
350     */
351    @Plugin(name = "String", category = CATEGORY)
352    public static class StringConverter implements TypeConverter<String> {
353        @Override
354        public String convert(final String s) {
355            return s;
356        }
357    }
358
359    /**
360     * Converts a {@link String} into a {@link URI}.
361     */
362    @Plugin(name = "URI", category = CATEGORY)
363    public static class UriConverter implements TypeConverter<URI> {
364        @Override
365        public URI convert(final String s) throws URISyntaxException {
366            return new URI(s);
367        }
368    }
369
370    /**
371     * Converts a {@link String} into a {@link URL}.
372     */
373    @Plugin(name = "URL", category = CATEGORY)
374    public static class UrlConverter implements TypeConverter<URL> {
375        @Override
376        public URL convert(final String s) throws MalformedURLException {
377            return new URL(s);
378        }
379    }
380
381    /**
382     * Converts a {@link String} into a {@link UUID}.
383     * @since 2.8
384     */
385    @Plugin(name = "UUID", category = CATEGORY)
386    public static class UuidConverter implements TypeConverter<UUID> {
387        @Override
388        public UUID convert(final String s) throws Exception {
389            return UUID.fromString(s);
390        }
391    }
392
393    /**
394     * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided
395     * default value if the conversion is unsuccessful. However, if the default value is <em>also</em> invalid, then
396     * {@code null} is returned (along with a nasty status log message).
397     * 
398     * @param s
399     *        the string to convert
400     * @param clazz
401     *        the class to try to convert the string to
402     * @param defaultValue
403     *        the fallback object to use if the conversion is unsuccessful
404     * @return the converted object which may be {@code null} if the string is invalid for the given type
405     * @throws NullPointerException
406     *         if {@code clazz} is {@code null}
407     * @throws IllegalArgumentException
408     *         if no TypeConverter exists for the given class
409     */
410    public static <T> T convert(final String s, final Class<? extends T> clazz, final Object defaultValue) {
411        @SuppressWarnings("unchecked")
412        final TypeConverter<T> converter = (TypeConverter<T>) TypeConverterRegistry.getInstance().findCompatibleConverter(clazz);
413        if (s == null) {
414            // don't debug print here, resulting output is hard to understand
415            // LOGGER.debug("Null string given to convert. Using default [{}].", defaultValue);
416            return parseDefaultValue(converter, defaultValue);
417        }
418        try {
419            return converter.convert(s);
420        } catch (final Exception e) {
421            LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].", s, clazz,
422                    defaultValue, e);
423            return parseDefaultValue(converter, defaultValue);
424        }
425    }
426
427    @SuppressWarnings("unchecked")
428    private static <T> T parseDefaultValue(final TypeConverter<T> converter, final Object defaultValue) {
429        if (defaultValue == null) {
430            return null;
431        }
432        if (!(defaultValue instanceof String)) {
433            return (T) defaultValue;
434        }
435        try {
436            return converter.convert((String) defaultValue);
437        } catch (final Exception e) {
438            LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e);
439            return null;
440        }
441    }
442
443    private static final Logger LOGGER = StatusLogger.getLogger();
444
445}