001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
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 * Contributors:
017 *     Thierry Delprat
018 */
019package org.nuxeo.elasticsearch.audit.io;
020
021import static org.nuxeo.common.utils.DateUtils.formatISODateTime;
022import static org.nuxeo.common.utils.DateUtils.nowIfNull;
023import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_CATEGORY;
024import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_COMMENT;
025import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_LIFE_CYCLE;
026import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_PATH;
027import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_TYPE;
028import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_UUID;
029import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EVENT_DATE;
030import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EVENT_ID;
031import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EXTENDED;
032import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_ID;
033import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_LOG_DATE;
034import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_PRINCIPAL_NAME;
035import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_REPOSITORY_ID;
036
037import java.io.IOException;
038import java.io.Serializable;
039import java.util.Map;
040
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.nuxeo.ecm.core.api.impl.blob.AbstractBlob;
044import org.nuxeo.ecm.platform.audit.api.ExtendedInfo;
045import org.nuxeo.ecm.platform.audit.api.LogEntry;
046
047import com.fasterxml.jackson.core.JsonGenerationException;
048import com.fasterxml.jackson.core.JsonGenerator;
049import com.fasterxml.jackson.core.JsonProcessingException;
050import com.fasterxml.jackson.core.Version;
051import com.fasterxml.jackson.databind.JsonMappingException;
052import com.fasterxml.jackson.databind.JsonSerializer;
053import com.fasterxml.jackson.databind.ObjectMapper;
054import com.fasterxml.jackson.databind.SerializerProvider;
055import com.fasterxml.jackson.databind.module.SimpleModule;
056
057public class AuditEntryJSONWriter {
058
059    protected static final Log log = LogFactory.getLog(AuditEntryJSONWriter.class);
060
061    public static void asJSON(JsonGenerator jg, LogEntry logEntry) throws IOException {
062        ObjectMapper objectMapper = new ObjectMapper();
063        SimpleModule module = new SimpleModule("esAuditJson", Version.unknownVersion());
064        module.addSerializer(Map.class, new MapEntrySerializer());
065        module.addSerializer(AbstractBlob.class, new BinaryBlobEntrySerializer());
066        objectMapper.registerModule(module);
067        jg.setCodec(objectMapper);
068
069        jg.writeStartObject();
070        jg.writeStringField("entity-type", "logEntry");
071
072        writeField(jg, LOG_CATEGORY, logEntry.getCategory());
073        writeField(jg, LOG_PRINCIPAL_NAME, logEntry.getPrincipalName());
074        writeField(jg, LOG_COMMENT, logEntry.getComment());
075        writeField(jg, LOG_DOC_LIFE_CYCLE, logEntry.getDocLifeCycle());
076        writeField(jg, LOG_DOC_PATH, logEntry.getDocPath());
077        writeField(jg, LOG_DOC_TYPE, logEntry.getDocType());
078        writeField(jg, LOG_DOC_UUID, logEntry.getDocUUID());
079        writeField(jg, LOG_EVENT_ID, logEntry.getEventId());
080        writeField(jg, LOG_REPOSITORY_ID, logEntry.getRepositoryId());
081        jg.writeStringField(LOG_EVENT_DATE, formatISODateTime(nowIfNull(logEntry.getEventDate())));
082        jg.writeNumberField(LOG_ID, logEntry.getId());
083        jg.writeStringField(LOG_LOG_DATE, formatISODateTime(nowIfNull(logEntry.getLogDate())));
084        Map<String, ExtendedInfo> extended = logEntry.getExtendedInfos();
085        jg.writeObjectFieldStart(LOG_EXTENDED);
086        for (String key : extended.keySet()) {
087            ExtendedInfo ei = extended.get(key);
088            if (ei != null && ei.getSerializableValue() != null) {
089                Serializable value = ei.getSerializableValue();
090                if (value instanceof String) {
091                    String strValue = (String) value;
092                    if (isJsonContent(strValue)) {
093                        jg.writeFieldName(key);
094                        jg.writeRawValue(strValue);
095                    } else {
096                        jg.writeStringField(key, strValue);
097                    }
098                } else {
099                    try {
100                        jg.writeObjectField(key, ei.getSerializableValue());
101                    } catch (JsonMappingException e) {
102                        log.error("No Serializer found.", e);
103                    }
104                }
105            } else {
106                jg.writeNullField(key);
107            }
108        }
109        jg.writeEndObject();
110
111        jg.writeEndObject();
112        jg.flush();
113    }
114
115    /**
116     * Helper method used to determine if a String field is actually nested JSON
117     *
118     * @since 7.4
119     */
120    protected static boolean isJsonContent(String value) {
121        if (value != null) {
122            value = value.trim();
123            if (value.startsWith("{") && value.endsWith("}")) {
124                return true;
125            } else if (value.startsWith("[") && value.endsWith("]")) {
126                return true;
127            }
128        }
129        return false;
130    }
131
132    protected static void writeField(JsonGenerator jg, String name, String value) throws IOException {
133        if (value == null) {
134            jg.writeNullField(name);
135        } else {
136            jg.writeStringField(name, value);
137        }
138    }
139
140    @SuppressWarnings("rawtypes")
141    static class MapEntrySerializer extends JsonSerializer<Map> {
142
143        @Override
144        public void serialize(Map map, JsonGenerator jgen, SerializerProvider provider) throws IOException,
145                JsonProcessingException {
146            jgen.writeStartObject();
147            for (Object key : map.keySet()) {
148                jgen.writeObjectField((String) key, map.get(key));
149            }
150            jgen.writeEndObject();
151        }
152    }
153
154    static class BinaryBlobEntrySerializer extends JsonSerializer<AbstractBlob> {
155
156        @Override
157        public void serialize(AbstractBlob blob, JsonGenerator jgen, SerializerProvider provider)
158                throws JsonGenerationException, IOException {
159            // Do not serizalize
160            jgen.writeNull();
161        }
162    }
163
164}