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    
018    package org.apache.logging.log4j.core.config.plugins.util;
019    
020    import java.io.File;
021    import java.math.BigDecimal;
022    import java.math.BigInteger;
023    import java.net.MalformedURLException;
024    import java.net.URI;
025    import java.net.URISyntaxException;
026    import java.net.URL;
027    import java.nio.charset.Charset;
028    import java.security.Provider;
029    import java.security.Security;
030    import java.util.Map;
031    import java.util.concurrent.ConcurrentHashMap;
032    import java.util.regex.Pattern;
033    
034    import javax.xml.bind.DatatypeConverter;
035    
036    import org.apache.logging.log4j.Level;
037    import org.apache.logging.log4j.Logger;
038    import org.apache.logging.log4j.core.Filter;
039    import org.apache.logging.log4j.core.layout.HtmlLayout;
040    import org.apache.logging.log4j.core.net.Facility;
041    import org.apache.logging.log4j.core.net.Protocol;
042    import org.apache.logging.log4j.core.util.Assert;
043    import org.apache.logging.log4j.core.util.Loader;
044    import org.apache.logging.log4j.status.StatusLogger;
045    import org.apache.logging.log4j.util.EnglishEnums;
046    
047    /**
048     * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find
049     * registered TypeConverters.
050     */
051    public final class TypeConverters {
052    
053        // TODO: this could probably be combined with the usual plugin architecture instead
054    
055        /**
056         * Parses a {@link String} into a {@link BigDecimal}.
057         */
058        public static class BigDecimalConverter implements TypeConverter<BigDecimal> {
059            @Override
060            public BigDecimal convert(final String s) {
061                return new BigDecimal(s);
062            }
063        }
064    
065        /**
066         * Parses a {@link String} into a {@link BigInteger}.
067         */
068        public static class BigIntegerConverter implements TypeConverter<BigInteger> {
069            @Override
070            public BigInteger convert(final String s) {
071                return new BigInteger(s);
072            }
073        }
074    
075        /**
076         * Converts a {@link String} into a {@link Boolean}.
077         */
078        public static class BooleanConverter implements TypeConverter<Boolean> {
079            @Override
080            public Boolean convert(final String s) {
081                return Boolean.valueOf(s);
082            }
083        }
084    
085        /**
086         * Converts a {@link String} into a {@code byte[]}.
087         * 
088         * The supported formats are:
089         * <ul>
090         * <li>0x0123456789ABCDEF</li>
091         * <li>Base64:ABase64String</li>
092         * <li>String</li>
093         * </ul>
094         */
095        public static class ByteArrayConverter implements TypeConverter<byte[]> {
096    
097            private static final String PREFIX_0x = "0x";
098            private static final String PREFIX_BASE64 = "Base64:";
099    
100            @Override
101            public byte[] convert(final String value) {
102                byte[] bytes;
103                if (value == null || value.isEmpty()) {
104                    bytes = new byte[0];
105                } else if (value.startsWith(PREFIX_BASE64)) {
106                    final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length());
107                    bytes = DatatypeConverter.parseBase64Binary(lexicalXSDBase64Binary);
108                } else if (value.startsWith(PREFIX_0x)) {
109                    final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length());
110                    bytes = DatatypeConverter.parseHexBinary(lexicalXSDHexBinary);
111                } else {
112                    bytes = value.getBytes(Charset.defaultCharset());
113                }
114                return bytes;
115            }
116        }
117    
118        /**
119         * Converts a {@link String} into a {@link Byte}.
120         */
121        public static class ByteConverter implements TypeConverter<Byte> {
122            @Override
123            public Byte convert(final String s) {
124                return Byte.valueOf(s);
125            }
126        }
127    
128        /**
129         * Converts a {@link String} into a {@link Character}.
130         */
131        public static class CharacterConverter implements TypeConverter<Character> {
132            @Override
133            public Character convert(final String s) {
134                if (s.length() != 1) {
135                    throw new IllegalArgumentException("Character string must be of length 1: " + s);
136                }
137                return Character.valueOf(s.toCharArray()[0]);
138            }
139        }
140    
141        /**
142         * Converts a {@link String} into a {@code char[]}.
143         */
144        public static class CharArrayConverter implements TypeConverter<char[]> {
145            @Override
146            public char[] convert(final String s) {
147                return s.toCharArray();
148            }
149        }
150    
151        /**
152         * Converts a {@link String} into a {@link Charset}.
153         */
154        public static class CharsetConverter implements TypeConverter<Charset> {
155            @Override
156            public Charset convert(final String s) {
157                return Charset.forName(s);
158            }
159        }
160    
161        /**
162         * Converts a {@link String} into a {@link Class}.
163         */
164        public static class ClassConverter implements TypeConverter<Class<?>> {
165            @Override
166            public Class<?> convert(final String s) throws ClassNotFoundException {
167                return Loader.loadClass(s);
168            }
169        }
170    
171        /**
172         * Converts a {@link String} into a {@link Double}.
173         */
174        public static class DoubleConverter implements TypeConverter<Double> {
175            @Override
176            public Double convert(final String s) {
177                return Double.valueOf(s);
178            }
179        }
180    
181        /**
182         * Converts a {@link String} into a {@link Enum}. Returns {@code null} for invalid enum names.
183         * 
184         * @param <E>
185         *        the enum class to parse.
186         */
187        public static class EnumConverter<E extends Enum<E>> implements TypeConverter<E> {
188            private final Class<E> clazz;
189    
190            private EnumConverter(final Class<E> clazz) {
191                this.clazz = clazz;
192            }
193    
194            @Override
195            public E convert(final String s) {
196                return EnglishEnums.valueOf(clazz, s);
197            }
198        }
199    
200        /**
201         * Converts a {@link String} into a {@link File}.
202         */
203        public static class FileConverter implements TypeConverter<File> {
204            @Override
205            public File convert(final String s) {
206                return new File(s);
207            }
208        }
209    
210        /**
211         * Converts a {@link String} into a {@link Float}.
212         */
213        public static class FloatConverter implements TypeConverter<Float> {
214            @Override
215            public Float convert(final String s) {
216                return Float.valueOf(s);
217            }
218        }
219    
220        private static final class Holder {
221            private static final TypeConverters INSTANCE = new TypeConverters();
222        }
223    
224        /**
225         * Converts a {@link String} into a {@link Integer}.
226         */
227        public static class IntegerConverter implements TypeConverter<Integer> {
228            @Override
229            public Integer convert(final String s) {
230                return Integer.valueOf(s);
231            }
232        }
233    
234        /**
235         * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names.
236         */
237        public static class LevelConverter implements TypeConverter<Level> {
238            @Override
239            public Level convert(final String s) {
240                return Level.valueOf(s);
241            }
242        }
243    
244        /**
245         * Converts a {@link String} into a {@link Long}.
246         */
247        public static class LongConverter implements TypeConverter<Long> {
248            @Override
249            public Long convert(final String s) {
250                return Long.valueOf(s);
251            }
252        }
253    
254        /**
255         * Converts a {@link String} into a {@link Pattern}.
256         */
257        public static class PatternConverter implements TypeConverter<Pattern> {
258            @Override
259            public Pattern convert(final String s) {
260                return Pattern.compile(s);
261            }
262        }
263    
264        /**
265         * Converts a {@link String} into a {@link Pattern}.
266         */
267        public static class SecurityProviderConverter implements TypeConverter<Provider> {
268            @Override
269            public Provider convert(final String s) {
270                return Security.getProvider(s);
271            }
272        }
273    
274        /**
275         * Converts a {@link String} into a {@link Short}.
276         */
277        public static class ShortConverter implements TypeConverter<Short> {
278            @Override
279            public Short convert(final String s) {
280                return Short.valueOf(s);
281            }
282        }
283    
284        /**
285         * Returns the given {@link String}, no conversion takes place.
286         */
287        public static class StringConverter implements TypeConverter<String> {
288            @Override
289            public String convert(final String s) {
290                return s;
291            }
292        }
293    
294        /**
295         * Converts a {@link String} into a {@link URI}.
296         */
297        public static class UriConverter implements TypeConverter<URI> {
298            @Override
299            public URI convert(final String s) throws URISyntaxException {
300                return new URI(s);
301            }
302        }
303    
304        /**
305         * Converts a {@link String} into a {@link URL}.
306         */
307        public static class UrlConverter implements TypeConverter<URL> {
308            @Override
309            public URL convert(final String s) throws MalformedURLException {
310                return new URL(s);
311            }
312        }
313    
314        /**
315         * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided
316         * default value if the conversion is unsuccessful. However, if the default value is <em>also</em> invalid, then
317         * {@code null} is returned (along with a nasty status log message).
318         * 
319         * @param s
320         *        the string to convert
321         * @param clazz
322         *        the class to try to convert the string to
323         * @param defaultValue
324         *        the fallback object to use if the conversion is unsuccessful
325         * @return the converted object which may be {@code null} if the string is invalid for the given type
326         * @throws NullPointerException
327         *         if {@code clazz} is {@code null}
328         * @throws IllegalArgumentException
329         *         if no TypeConverter exists for the given class
330         */
331        public static Object convert(final String s, final Class<?> clazz, final Object defaultValue) {
332            final TypeConverter<?> converter = findTypeConverter(Assert.requireNonNull(clazz,
333                    "No class specified to convert to."));
334            if (converter == null) {
335                throw new IllegalArgumentException("No type converter found for class: " + clazz.getName());
336            }
337            if (s == null) {
338                // don't debug print here, resulting output is hard to understand
339                //LOGGER.debug("Null string given to convert. Using default [{}].", defaultValue);
340                return parseDefaultValue(converter, defaultValue);
341            }
342            try {
343                return converter.convert(s);
344            } catch (final Exception e) {
345                LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].", s, clazz,
346                        defaultValue, e);
347                return parseDefaultValue(converter, defaultValue);
348            }
349        }
350    
351        /**
352         * Locates a TypeConverter for a specified class.
353         * 
354         * @param clazz
355         *        the class to get a TypeConverter for
356         * @return the associated TypeConverter for that class or {@code null} if none could be found
357         */
358        public static TypeConverter<?> findTypeConverter(final Class<?> clazz) {
359            // TODO: what to do if there's no converter?
360            // supplementary idea: automatically add type converters for enums using EnglishEnums
361            // Idea 1: use reflection to see if the class has a static "valueOf" method and use that
362            // Idea 2: reflect on class's declared methods to see if any methods look suitable (probably too complex)
363            return Holder.INSTANCE.registry.get(clazz);
364        }
365    
366        private static Object parseDefaultValue(final TypeConverter<?> converter, final Object defaultValue) {
367            if (defaultValue == null) {
368                return null;
369            }
370            if (!(defaultValue instanceof String)) {
371                return defaultValue;
372            }
373            try {
374                return converter.convert((String) defaultValue);
375            } catch (final Exception e) {
376                LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e);
377                return null;
378            }
379        }
380    
381        /**
382         * Registers a TypeConverter for a specified class. This will overwrite any existing TypeConverter that may be
383         * registered for the class.
384         * 
385         * @param clazz
386         *        the class to register the TypeConverter for
387         * @param converter
388         *        the TypeConverter to register
389         */
390        public static void registerTypeConverter(final Class<?> clazz, final TypeConverter<?> converter) {
391            Holder.INSTANCE.registry.put(clazz, converter);
392        }
393    
394        private static final Logger LOGGER = StatusLogger.getLogger();
395    
396        private final Map<Class<?>, TypeConverter<?>> registry = new ConcurrentHashMap<Class<?>, TypeConverter<?>>();
397    
398        /**
399         * Constructs default TypeConverter registry. Used solely by singleton instance.
400         */
401        private TypeConverters() {
402            // Primitive wrappers
403            registry.put(Boolean.class, new BooleanConverter());
404            registry.put(Byte.class, new ByteConverter());
405            registry.put(Character.class, new CharacterConverter());
406            registry.put(Double.class, new DoubleConverter());
407            registry.put(Float.class, new FloatConverter());
408            registry.put(Integer.class, new IntegerConverter());
409            registry.put(Long.class, new LongConverter());
410            registry.put(Short.class, new ShortConverter());
411            // Primitives
412            registry.put(boolean.class, registry.get(Boolean.class));
413            registry.put(byte.class, new ByteConverter());
414            registry.put(char[].class, new CharArrayConverter());
415            registry.put(double.class, registry.get(Double.class));
416            registry.put(float.class, registry.get(Float.class));
417            registry.put(int.class, registry.get(Integer.class));
418            registry.put(long.class, registry.get(Long.class));
419            registry.put(short.class, registry.get(Short.class));
420            // Primitive arrays
421            registry.put(byte[].class, new ByteArrayConverter());
422            registry.put(char.class, new CharacterConverter());
423            // Numbers
424            registry.put(BigInteger.class, new BigIntegerConverter());
425            registry.put(BigDecimal.class, new BigDecimalConverter());
426            // JRE
427            registry.put(String.class, new StringConverter());
428            registry.put(Charset.class, new CharsetConverter());
429            registry.put(File.class, new FileConverter());
430            registry.put(URL.class, new UrlConverter());
431            registry.put(URI.class, new UriConverter());
432            registry.put(Class.class, new ClassConverter());
433            registry.put(Pattern.class, new PatternConverter());
434            registry.put(Provider.class, new SecurityProviderConverter());
435            // Log4J
436            registry.put(Level.class, new LevelConverter());
437            registry.put(Filter.Result.class, new EnumConverter<Filter.Result>(Filter.Result.class));
438            registry.put(Facility.class, new EnumConverter<Facility>(Facility.class));
439            registry.put(Protocol.class, new EnumConverter<Protocol>(Protocol.class));
440            registry.put(HtmlLayout.FontSize.class, new EnumConverter<HtmlLayout.FontSize>(HtmlLayout.FontSize.class));
441        }
442    }