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; 020 021import com.fasterxml.jackson.databind.ObjectMapper; 022import com.fasterxml.jackson.databind.jsontype.NamedType; 023import org.jpos.log.evt.Connect; 024import org.jpos.log.evt.Deploy; 025import org.jpos.log.evt.DeployActivity; 026import org.jpos.log.evt.Disconnect; 027import org.jpos.log.evt.License; 028import org.jpos.log.evt.Listen; 029import org.jpos.log.evt.LogMessage; 030import org.jpos.log.evt.SessionEnd; 031import org.jpos.log.evt.SessionStart; 032import org.jpos.log.evt.Shutdown; 033import org.jpos.log.evt.Start; 034import org.jpos.log.evt.Stop; 035import org.jpos.log.evt.SysInfo; 036import org.jpos.log.evt.ThrowableAuditLogEvent; 037import org.jpos.log.evt.Txn; 038import org.jpos.log.evt.UnDeploy; 039import org.jpos.log.evt.Warning; 040 041import java.util.Collection; 042import java.util.Collections; 043import java.util.LinkedHashMap; 044import java.util.List; 045import java.util.Map; 046import java.util.ServiceLoader; 047 048/** 049 * Registry of {@link AuditLogEvent} type mappings. 050 * 051 * <p>Holds the built-in type ids and discovers additional ones contributed by 052 * {@link AuditLogEventProvider} implementations through 053 * {@link ServiceLoader}. The result is a single source of truth used by every 054 * Jackson-based renderer in jPOS to register subtypes on its {@code ObjectMapper}.</p> 055 * 056 * <p>The registry is initialized lazily on first use. Built-in entries are 057 * always registered first so external providers cannot shadow them: any 058 * provider entry that re-uses a built-in id, or that collides with another 059 * provider entry, is rejected with {@link IllegalStateException}.</p> 060 * 061 * @since 3.0.0 062 */ 063public final class AuditLogEventRegistry { 064 private static final List<AuditLogEventType> BUILTINS = List.of( 065 new AuditLogEventType("warn", Warning.class), 066 new AuditLogEventType("start", Start.class), 067 new AuditLogEventType("stop", Stop.class), 068 new AuditLogEventType("deploy", Deploy.class), 069 new AuditLogEventType("undeploy", UnDeploy.class), 070 new AuditLogEventType("msg", LogMessage.class), 071 new AuditLogEventType("shutdown", Shutdown.class), 072 new AuditLogEventType("deploy-activity", DeployActivity.class), 073 new AuditLogEventType("throwable", ThrowableAuditLogEvent.class), 074 new AuditLogEventType("license", License.class), 075 new AuditLogEventType("sysinfo", SysInfo.class), 076 new AuditLogEventType("connect", Connect.class), 077 new AuditLogEventType("disconnect", Disconnect.class), 078 new AuditLogEventType("listen", Listen.class), 079 new AuditLogEventType("session-start", SessionStart.class), 080 new AuditLogEventType("session-end", SessionEnd.class), 081 new AuditLogEventType("txn", Txn.class) 082 ); 083 084 private static volatile Map<String, AuditLogEventType> types; 085 086 private AuditLogEventRegistry() { } 087 088 /** 089 * Returns every registered type mapping. 090 * 091 * @return all known type mappings, built-ins first, then ServiceLoader-discovered. 092 */ 093 public static Collection<AuditLogEventType> types() { 094 return load().values(); 095 } 096 097 /** 098 * Registers every known {@link AuditLogEvent} subtype on the given 099 * {@code ObjectMapper}. Renderers should call this once after constructing 100 * their mapper. 101 * 102 * @param <M> concrete mapper type, allowing fluent chaining on subclasses 103 * such as {@code XmlMapper} 104 * @param mapper the Jackson {@link ObjectMapper} (or subclass, e.g. {@code XmlMapper}) 105 * @return the same mapper for chaining 106 */ 107 public static <M extends ObjectMapper> M register(M mapper) { 108 for (AuditLogEventType t : load().values()) { 109 mapper.registerSubtypes(new NamedType(t.clazz(), t.name())); 110 } 111 return mapper; 112 } 113 114 /** 115 * Reloads the registry. Visible for testing — callers shouldn't need this in 116 * production, since mappings discovered through {@link ServiceLoader} are 117 * fixed for the lifetime of the JVM. 118 */ 119 static synchronized void reload() { 120 types = null; 121 load(); 122 } 123 124 private static Map<String, AuditLogEventType> load() { 125 Map<String, AuditLogEventType> snapshot = types; 126 if (snapshot != null) 127 return snapshot; 128 synchronized (AuditLogEventRegistry.class) { 129 if (types != null) 130 return types; 131 Map<String, AuditLogEventType> map = new LinkedHashMap<>(); 132 for (AuditLogEventType t : BUILTINS) 133 map.put(t.name(), t); 134 for (AuditLogEventProvider provider : ServiceLoader.load(AuditLogEventProvider.class)) { 135 Collection<AuditLogEventType> contributed = provider.types(); 136 if (contributed == null) 137 continue; 138 for (AuditLogEventType t : contributed) { 139 AuditLogEventType existing = map.get(t.name()); 140 if (existing != null && !existing.equals(t)) { 141 throw new IllegalStateException( 142 "AuditLogEventProvider " + provider.getClass().getName() 143 + " attempted to register conflicting type id '" + t.name() 144 + "' (already mapped to " + existing.clazz().getName() + ")"); 145 } 146 map.put(t.name(), t); 147 } 148 } 149 types = Collections.unmodifiableMap(map); 150 return types; 151 } 152 } 153}