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 /** Creates a writer with no mappers configured; mappers are wired by {@link #setConfiguration(Element)}. */ 059 public MappingLogEventWriter() {} 060 List<LogEventMapper> eventMappers; 061 List<ByteArrayMapper> outputMappers; 062 ByteArrayOutputStream captureOutputStream; 063 PrintStream capturePrintStream; 064 065 @Override 066 public void setPrintStream(PrintStream p) { 067 super.setPrintStream(p); 068 if (p != null && capturePrintStream == null) { 069 configureCaptureStreams(); 070 } 071 } 072 073 @Override 074 public synchronized void close() { 075 if (capturePrintStream != null) { 076 capturePrintStream.close(); 077 capturePrintStream = null; 078 captureOutputStream = null; 079 } 080 super.close(); 081 } 082 083 @Override 084 public void write(LogEvent ev) { 085 ev = mapEvents(ev); 086 if (capturePrintStream != null) { 087 writeToCaptureStream(ev); 088 try { 089 byte[] output = mapOutput(captureOutputStream.toByteArray()); 090 p.write(output); 091 } catch (IOException e) { 092 e.printStackTrace(p); 093 } finally { 094 p.flush(); 095 captureOutputStream.reset(); 096 } 097 } else { 098 delegateWriteToSuper(ev); 099 } 100 } 101 102 @Override 103 public void setConfiguration(Element e) throws ConfigurationException { 104 configureEventMappers(e); 105 configureOutputMappers(e); 106 } 107 108 /** 109 * Lazily allocates the capture stream pair when at least one output mapper is configured. 110 */ 111 protected void configureCaptureStreams() { 112 if (outputMappers != null && !outputMappers.isEmpty()) { 113 captureOutputStream = new ByteArrayOutputStream(); 114 capturePrintStream = new PrintStream(captureOutputStream); 115 } 116 } 117 118 /** 119 * Reads {@code <event-mapper>} children from {@code e} and instantiates each. 120 * 121 * @param e XML configuration element 122 * @throws ConfigurationException if any mapper class cannot be instantiated or configured 123 */ 124 protected void configureEventMappers(Element e) throws ConfigurationException { 125 List<Element> eventMappers = e.getChildren("event-mapper"); 126 LogEventMapper mapper; 127 for (Element em : eventMappers) { 128 String clazz = em.getAttributeValue("class"); 129 if (clazz != null) { 130 try { 131 mapper = (LogEventMapper) Class.forName(clazz).newInstance(); 132 } catch (Exception ex) { 133 throw new ConfigurationException(ex); 134 } 135 if (mapper != null) { 136 if (mapper instanceof Configurable) { 137 SimpleConfigurationFactory factory = new SimpleConfigurationFactory(); 138 ((Configurable) mapper).setConfiguration(factory.getConfiguration(em)); 139 } 140 if (mapper instanceof XmlConfigurable) { 141 ((XmlConfigurable) mapper).setConfiguration(em); 142 } 143 if (this.eventMappers == null) { 144 this.eventMappers = new ArrayList<>(); 145 } 146 this.eventMappers.add(mapper); 147 } 148 } 149 } 150 } 151 152 /** 153 * Reads {@code <output-mapper>} children from {@code e} and instantiates each. 154 * 155 * @param e XML configuration element 156 * @throws ConfigurationException if any mapper class cannot be instantiated or configured 157 */ 158 protected void configureOutputMappers(Element e) throws ConfigurationException { 159 List<Element> outputMappers = e.getChildren("output-mapper"); 160 ByteArrayMapper mapper; 161 for (Element em : outputMappers) { 162 String clazz = em.getAttributeValue("class"); 163 if (clazz != null) { 164 try { 165 mapper = (ByteArrayMapper) Class.forName(clazz).newInstance(); 166 } catch (Exception ex) { 167 throw new ConfigurationException(ex); 168 } 169 if (mapper != null) { 170 if (mapper instanceof Configurable) { 171 SimpleConfigurationFactory factory = new SimpleConfigurationFactory(); 172 ((Configurable) mapper).setConfiguration(factory.getConfiguration(em)); 173 } 174 if (mapper instanceof XmlConfigurable) { 175 ((XmlConfigurable) mapper).setConfiguration(em); 176 } 177 if (this.outputMappers == null) { 178 this.outputMappers = new ArrayList<>(); 179 } 180 this.outputMappers.add(mapper); 181 } 182 } 183 } 184 } 185 186 /** 187 * Applies every registered event mapper, in order, to {@code ev}. 188 * 189 * @param ev event to transform 190 * @return the transformed event 191 */ 192 protected LogEvent mapEvents(LogEvent ev) { 193 if (eventMappers != null) { 194 for (LogEventMapper mapper : eventMappers) { 195 ev = mapper.apply(ev); 196 } 197 } 198 return ev; 199 } 200 201 /** 202 * Applies every registered output mapper, in order, to the captured output bytes. 203 * 204 * @param output bytes captured from {@link #writeToCaptureStream(LogEvent)} 205 * @return the transformed output bytes 206 */ 207 protected byte[] mapOutput(byte[] output) { 208 if (outputMappers != null) { 209 for (ByteArrayMapper mapper : outputMappers) { 210 output = mapper.apply(output); 211 } 212 } 213 return output; 214 } 215 216 /** 217 * This method exists and is used so that we can verify the order of instructions 218 * during a call to write in unit tests. 219 * 220 * @param ev LogEvent to write. 221 */ 222 protected void delegateWriteToSuper(LogEvent ev) { 223 super.write(ev); 224 } 225 226 /** 227 * Write to capture print stream when defined. 228 * @param ev LogEvent to write. 229 */ 230 protected void writeToCaptureStream(LogEvent ev) { 231 if (capturePrintStream != null && ev != null) { 232 ev.dump(capturePrintStream, ""); 233 capturePrintStream.flush(); 234 } 235 } 236}