001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.log.render.xml;
020
021import com.fasterxml.jackson.core.JsonProcessingException;
022import com.fasterxml.jackson.databind.SerializationFeature;
023import com.fasterxml.jackson.databind.module.SimpleModule;
024import com.fasterxml.jackson.dataformat.xml.XmlMapper;
025import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
026import org.jpos.log.AuditLogEvent;
027import org.jpos.log.AuditLogEventRegistry;
028
029import org.jpos.log.LogRenderer;
030import org.jpos.log.evt.LogEvt;
031import org.jpos.log.evt.LogMessage;
032import org.jpos.log.evt.ThrowableAuditLogEvent;
033import org.jpos.log.render.ThrowableSerializer;
034import org.jpos.util.LogEvent;
035import org.jpos.util.Loggeable;
036
037import java.io.ByteArrayOutputStream;
038import java.io.PrintStream;
039import java.time.Duration;
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Map;
043
044/** XML renderer for {@link LogEvent}, emitting wrapped {@code <log>} elements with payload children. */
045public final class LogEventXmlLogRenderer implements LogRenderer<LogEvent> {
046    private final XmlMapper mapper = new XmlMapper();
047
048    /** Default constructor. */
049    public LogEventXmlLogRenderer() {
050        mapper.registerModule(new JavaTimeModule());
051        mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
052
053        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
054        mapper.enable(SerializationFeature.INDENT_OUTPUT);
055
056        SimpleModule module = new SimpleModule();
057        module.addSerializer(Throwable.class, new ThrowableSerializer());
058        mapper.registerModule(module);
059        AuditLogEventRegistry.register(mapper);
060    }
061
062    @Override
063    public void render(LogEvent evt, PrintStream ps, String indent) {
064        List<AuditLogEvent> events = evt.getPayLoad()
065          .stream()
066          .map (obj -> switch (obj) {
067              case AuditLogEvent ale -> ale;
068              case Throwable t -> new ThrowableAuditLogEvent(t);
069              default -> new LogMessage(dump(obj));
070          }).toList();
071        long elapsed = Duration.between(evt.getCreatedAt(), evt.getDumpedAt()).toMillis();
072        LogEvt ev = new LogEvt (
073          evt.getDumpedAt(),
074          evt.getTag(),
075          elapsed == 0L ? null : elapsed,
076          buildTags(evt),
077          events
078        );
079        try {
080            ps.println (mapper.writeValueAsString(ev));
081        } catch (JsonProcessingException e) {
082            ps.print (kv("exception", e.toString()));
083        }
084    }
085    public Class<?> clazz() {
086        return LogEvent.class;
087    }
088    public Type type() {
089        return Type.XML;
090    }
091
092    private Map<String,String> buildTags(LogEvent evt) {
093        evt.getTraceId(); // ensure trace-id is generated
094        Map<String,String> tags = new LinkedHashMap<>();
095        String realm = evt.getRealm();
096        if (realm != null && !realm.isEmpty())
097            tags.put("realm", realm);
098        tags.putAll(evt.getTags());
099        return tags;
100    }
101
102    private String kv (String k, String v) {
103        return "{\"%s\":\"%s\"}".formatted(k,v);
104    }
105
106    private String dump (Object obj) {
107        if (obj instanceof Loggeable loggeable) {
108            ByteArrayOutputStream baos = new ByteArrayOutputStream();
109            PrintStream ps = new PrintStream(baos);
110            loggeable.dump(ps, "");
111            return baos.toString();
112        }
113        return obj.toString();
114    }
115}