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 */
017package org.apache.logging.log4j.core.appender.db.jpa.converter;
018
019import java.io.IOException;
020import java.util.Iterator;
021import java.util.Map;
022import javax.persistence.AttributeConverter;
023import javax.persistence.Converter;
024import javax.persistence.PersistenceException;
025
026import org.apache.logging.log4j.util.ReadOnlyStringMap;
027import org.apache.logging.log4j.core.impl.ContextDataFactory;
028import org.apache.logging.log4j.util.StringMap;
029import org.apache.logging.log4j.util.BiConsumer;
030import org.apache.logging.log4j.util.Strings;
031
032import com.fasterxml.jackson.databind.JsonNode;
033import com.fasterxml.jackson.databind.ObjectMapper;
034import com.fasterxml.jackson.databind.node.JsonNodeFactory;
035import com.fasterxml.jackson.databind.node.ObjectNode;
036
037/**
038 * A JPA 2.1 attribute converter for {@link ReadOnlyStringMap}s in
039 * {@link org.apache.logging.log4j.core.LogEvent}s. This converter is capable of converting both to and from
040 * {@link String}s.
041 *
042 * In addition to other optional dependencies required by the JPA appender, this converter requires the Jackson Data
043 * Processor.
044 */
045@Converter(autoApply = false)
046public class ContextDataJsonAttributeConverter implements AttributeConverter<ReadOnlyStringMap, String> {
047    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
048
049    @Override
050    public String convertToDatabaseColumn(final ReadOnlyStringMap contextData) {
051        if (contextData == null) {
052            return null;
053        }
054
055        try {
056            final JsonNodeFactory factory = OBJECT_MAPPER.getNodeFactory();
057            final ObjectNode root = factory.objectNode();
058            contextData.forEach(new BiConsumer<String, Object>() {
059                @Override
060                public void accept(final String key, final Object value) {
061                    // we will cheat here and write the toString of the Object... meh, but ok.
062                    root.put(key, String.valueOf(value));
063                }
064            });
065            return OBJECT_MAPPER.writeValueAsString(root);
066        } catch (final Exception e) {
067            throw new PersistenceException("Failed to convert contextData to JSON string.", e);
068        }
069    }
070
071    @Override
072    public ReadOnlyStringMap convertToEntityAttribute(final String s) {
073        if (Strings.isEmpty(s)) {
074            return null;
075        }
076        try {
077            final StringMap result = ContextDataFactory.createContextData();
078            final ObjectNode root = (ObjectNode) OBJECT_MAPPER.readTree(s);
079            final Iterator<Map.Entry<String, JsonNode>> entries = root.fields();
080            while (entries.hasNext()) {
081                final Map.Entry<String, JsonNode> entry = entries.next();
082
083                // Don't know what to do with non-text values.
084                // Maybe users who need this need to provide custom converter?
085                final Object value = entry.getValue().textValue();
086                result.putValue(entry.getKey(), value);
087            }
088            return result;
089        } catch (final IOException e) {
090            throw new PersistenceException("Failed to convert JSON string to map.", e);
091        }
092    }
093}