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