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.util;
020
021import org.jdom2.Element;
022import org.jpos.core.Configurable;
023import org.jpos.core.ConfigurationException;
024import org.jpos.core.XmlConfigurable;
025import org.jpos.q2.SimpleConfigurationFactory;
026import org.jpos.util.function.ByteArrayMapper;
027import org.jpos.util.function.LogEventMapper;
028
029import java.io.ByteArrayOutputStream;
030import java.io.IOException;
031import java.io.PrintStream;
032import java.util.ArrayList;
033import java.util.List;
034
035/**
036 * LogEventWriter that can be configured with event and output mappers to modify
037 * the events before writing to output stream and modify the output stream before writing
038 * to the final destination respectfully.
039 *
040 * Example configuration: <br>
041 *     <pre>{@code
042 *         <writer class="org.jpos.util.MappingLogEventWriter">
043 *             <event-mapper class="...">
044 *                 <property....  />
045 *             </event-mapper>
046 *             <event-mapper class="..."/>
047 *             <output-mapper class="...">
048 *                 <property.... />
049 *             </output-mapper>
050 *             <output-mapper class="..."/>
051 *         </writer>
052 *     }</pre>
053 *
054 * @author Alwyn Schoeman
055 * @since 2.1.4
056 */
057public class MappingLogEventWriter extends BaseLogEventWriter implements XmlConfigurable {
058    List<LogEventMapper> eventMappers;
059    List<ByteArrayMapper> outputMappers;
060    ByteArrayOutputStream captureOutputStream;
061    PrintStream capturePrintStream;
062
063    @Override
064    public void setPrintStream(PrintStream p) {
065        super.setPrintStream(p);
066        if (p != null && capturePrintStream == null) {
067            configureCaptureStreams();
068        }
069    }
070
071    @Override
072    public synchronized void close() {
073        if (capturePrintStream != null) {
074            capturePrintStream.close();
075            capturePrintStream = null;
076            captureOutputStream = null;
077        }
078        super.close();
079    }
080
081    @Override
082    public void write(LogEvent ev) {
083        ev = mapEvents(ev);
084        if (capturePrintStream != null) {
085            writeToCaptureStream(ev);
086            try {
087                byte[] output = mapOutput(captureOutputStream.toByteArray());
088                p.write(output);
089            } catch (IOException e) {
090                e.printStackTrace(p);
091            } finally {
092                p.flush();
093                captureOutputStream.reset();
094            }
095        } else {
096            delegateWriteToSuper(ev);
097        }
098    }
099
100    @Override
101    public void setConfiguration(Element e) throws ConfigurationException {
102        configureEventMappers(e);
103        configureOutputMappers(e);
104    }
105
106    protected void configureCaptureStreams() {
107        if (outputMappers != null && !outputMappers.isEmpty()) {
108            captureOutputStream = new ByteArrayOutputStream();
109            capturePrintStream = new PrintStream(captureOutputStream);
110        }
111    }
112
113    protected void configureEventMappers(Element e) throws ConfigurationException {
114        List<Element> eventMappers = e.getChildren("event-mapper");
115        LogEventMapper mapper;
116        for (Element em : eventMappers) {
117            String clazz = em.getAttributeValue("class");
118            if (clazz != null) {
119                try {
120                    mapper = (LogEventMapper) Class.forName(clazz).newInstance();
121                } catch (Exception ex) {
122                    throw new ConfigurationException(ex);
123                }
124                if (mapper != null) {
125                    if (mapper instanceof Configurable) {
126                        SimpleConfigurationFactory factory = new SimpleConfigurationFactory();
127                        ((Configurable) mapper).setConfiguration(factory.getConfiguration(em));
128                    }
129                    if (mapper instanceof XmlConfigurable) {
130                        ((XmlConfigurable) mapper).setConfiguration(em);
131                    }
132                    if (this.eventMappers == null) {
133                        this.eventMappers = new ArrayList<>();
134                    }
135                    this.eventMappers.add(mapper);
136                }
137            }
138        }
139    }
140
141    protected void configureOutputMappers(Element e) throws ConfigurationException {
142        List<Element> outputMappers = e.getChildren("output-mapper");
143        ByteArrayMapper mapper;
144        for (Element em : outputMappers) {
145            String clazz = em.getAttributeValue("class");
146            if (clazz != null) {
147                try {
148                    mapper = (ByteArrayMapper) Class.forName(clazz).newInstance();
149                } catch (Exception ex) {
150                    throw new ConfigurationException(ex);
151                }
152                if (mapper != null) {
153                    if (mapper instanceof Configurable) {
154                        SimpleConfigurationFactory factory = new SimpleConfigurationFactory();
155                        ((Configurable) mapper).setConfiguration(factory.getConfiguration(em));
156                    }
157                    if (mapper instanceof XmlConfigurable) {
158                        ((XmlConfigurable) mapper).setConfiguration(em);
159                    }
160                    if (this.outputMappers == null) {
161                        this.outputMappers = new ArrayList<>();
162                    }
163                    this.outputMappers.add(mapper);
164                }
165            }
166        }
167    }
168
169    protected LogEvent mapEvents(LogEvent ev) {
170        if (eventMappers != null) {
171            for (LogEventMapper mapper : eventMappers) {
172                ev = mapper.apply(ev);
173            }
174        }
175        return ev;
176    }
177
178    protected byte[] mapOutput(byte[] output) {
179        if (outputMappers != null) {
180            for (ByteArrayMapper mapper : outputMappers) {
181                output = mapper.apply(output);
182            }
183        }
184        return output;
185    }
186
187    /**
188     * This method exists and is used so that we can verify the order of instructions
189     * during a call to write in unit tests.
190     *
191     * @param ev LogEvent to write.
192     */
193    protected void delegateWriteToSuper(LogEvent ev) {
194        super.write(ev);
195    }
196
197    /**
198     * Write to capture print stream when defined.
199     * @param ev LogEvent to write.
200     */
201    protected void writeToCaptureStream(LogEvent ev) {
202        if (capturePrintStream != null && ev != null) {
203            ev.dump(capturePrintStream, "");
204            capturePrintStream.flush();
205        }
206    }
207}