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}