001/*
002 * (C) Copyright 2015-2017 Nuxeo (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 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.platform.audit.io;
021
022import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
023import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
024import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_CATEGORY;
025import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_COMMENT;
026import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_LIFE_CYCLE;
027import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_PATH;
028import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_TYPE;
029import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_DOC_UUID;
030import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EVENT_DATE;
031import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EVENT_ID;
032import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_EXTENDED;
033import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_ID;
034import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_LOG_DATE;
035import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_PRINCIPAL_NAME;
036import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_REPOSITORY_ID;
037
038import java.io.IOException;
039import java.io.Serializable;
040import java.util.Date;
041import java.util.List;
042import java.util.Map;
043import java.util.Map.Entry;
044import java.util.stream.Collectors;
045
046import org.apache.commons.lang3.ClassUtils;
047import org.joda.time.DateTime;
048import org.joda.time.format.DateTimeFormatter;
049import org.joda.time.format.ISODateTimeFormat;
050import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter;
051import org.nuxeo.ecm.core.io.marshallers.json.enrichers.AbstractJsonEnricher;
052import org.nuxeo.ecm.core.io.registry.reflect.Setup;
053import org.nuxeo.ecm.platform.audit.api.ExtendedInfo;
054import org.nuxeo.ecm.platform.audit.api.LogEntry;
055
056import com.fasterxml.jackson.core.JsonGenerator;
057
058/**
059 * Convert {@link LogEntry} to Json.
060 * <p>
061 * This marshaller is enrichable: register class implementing {@link AbstractJsonEnricher} and managing {@link LogEntry}
062 * .
063 * </p>
064 * <p>
065 * This marshaller is also extensible: extend it and simply override
066 * {@link ExtensibleEntityJsonWriter#extend(Object, JsonGenerator)}.
067 * </p>
068 * <p>
069 * Format is:
070 *
071 * <pre>
072 * {@code
073 * {
074 *   "entity-type":"logEntry",
075 *   "category": "LOG_ENTRY_CATEGORY",
076 *   "principalName": "LOG_ENTRY_PRINCIPAL",
077 *   "comment": "LOG_ENTRY_COMMENT",
078 *   "docLifeCycle": "DOC_LIFECYCLE",
079 *   "docPath": "DOC_PATH",
080 *   "docType": "DOC_TYPE",
081 *   "docUUID": "DOC_UUID",
082 *   "eventId": "EVENT_ID",
083 *   "repositoryId": "REPO_ID",
084 *   "eventDate": "LOG_EVENT_DATE",
085 *   "logDate": "LOG_DATE"
086 *             <-- contextParameters if there are enrichers activated
087 *             <-- additional property provided by extend() method
088 * }
089 * </pre>
090 * </p>
091 * @since 7.2
092 */
093@Setup(mode = SINGLETON, priority = REFERENCE)
094public class LogEntryJsonWriter extends ExtensibleEntityJsonWriter<LogEntry> {
095
096    public static final String ENTITY_TYPE = "logEntry";
097
098    public LogEntryJsonWriter() {
099        super(ENTITY_TYPE, LogEntry.class);
100    }
101
102    @Override
103    protected void writeEntityBody(LogEntry logEntry, JsonGenerator jg) throws IOException {
104        jg.writeNumberField(LOG_ID, logEntry.getId());
105        jg.writeStringField(LOG_CATEGORY, logEntry.getCategory());
106        jg.writeStringField(LOG_PRINCIPAL_NAME, logEntry.getPrincipalName());
107        jg.writeStringField(LOG_COMMENT, logEntry.getComment());
108        jg.writeStringField(LOG_DOC_LIFE_CYCLE, logEntry.getDocLifeCycle());
109        jg.writeStringField(LOG_DOC_PATH, logEntry.getDocPath());
110        jg.writeStringField(LOG_DOC_TYPE, logEntry.getDocType());
111        jg.writeStringField(LOG_DOC_UUID, logEntry.getDocUUID());
112        jg.writeStringField(LOG_EVENT_ID, logEntry.getEventId());
113        jg.writeStringField(LOG_REPOSITORY_ID, logEntry.getRepositoryId());
114        DateTimeFormatter dateTime = ISODateTimeFormat.dateTime();
115        jg.writeStringField(LOG_EVENT_DATE, dateTime.print(new DateTime(logEntry.getEventDate())));
116        jg.writeStringField(LOG_LOG_DATE, dateTime.print(new DateTime(logEntry.getLogDate())));
117        writeExtendedInfos(jg, logEntry);
118    }
119
120    protected void writeExtendedInfos(JsonGenerator jg, LogEntry logEntry) throws IOException {
121        Map<String, ExtendedInfo> extended = logEntry.getExtendedInfos();
122        jg.writeObjectFieldStart(LOG_EXTENDED);
123        for (String key : extended.keySet()) {
124            ExtendedInfo ei = extended.get(key);
125            if (ei != null && ei.getSerializableValue() != null) {
126                writeExtendedInfo(jg, key, ei.getSerializableValue());
127            } else {
128                jg.writeNullField(key);
129            }
130        }
131        jg.writeEndObject();
132    }
133
134    protected void writeExtendedInfo(JsonGenerator jg, String key, Serializable value) throws IOException {
135        Class<?> clazz = value.getClass();
136        if (Long.class.isAssignableFrom(clazz)) {
137            jg.writeNumberField(key, (Long) value);
138        } else if (Integer.class.isAssignableFrom(clazz)) {
139            jg.writeNumberField(key, (Integer) value);
140        } else if (Double.class.isAssignableFrom(clazz)) {
141            jg.writeNumberField(key, (Double) value);
142        } else if (Date.class.isAssignableFrom(clazz)) {
143            jg.writeStringField(key, ISODateTimeFormat.dateTime().print(new DateTime(value)));
144        } else if (String.class.isAssignableFrom(clazz)) {
145            jg.writeStringField(key, (String) value);
146        } else if (Boolean.class.isAssignableFrom(clazz)) {
147            jg.writeBooleanField(key, (Boolean) value);
148        } else if (clazz.isArray() || List.class.isAssignableFrom(clazz)) {
149            jg.writeObjectField(key, value);
150        } else if (Map.class.isAssignableFrom(clazz)) {
151            @SuppressWarnings("unchecked")
152            Map<String, Serializable> map = //
153                    ((Map<String, Serializable>) value).entrySet()
154                                                       .stream()
155                                                       // keep primitive values
156                                                       // blobs can be in WorkflowVariables for instance
157                                                       .filter(entry -> entry.getValue() != null
158                                                               && ClassUtils.isPrimitiveOrWrapper(
159                                                                       entry.getValue().getClass()))
160                                                       .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
161            jg.writeObjectField(key, map);
162        } else {
163            // mainly blobs
164            jg.writeStringField(key, value.toString());
165        }
166    }
167
168}