001/*
002 * Copyright 2025 devteam@scivicslab.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing,
011 * software distributed under the License is distributed on an
012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied.  See the License for the
014 * specific language governing permissions and limitations
015 * under the License.
016 */
017
018package com.scivicslab.actoriac.accumulator;
019
020import java.util.concurrent.ExecutionException;
021import java.util.logging.Level;
022import java.util.logging.Logger;
023
024import org.json.JSONObject;
025
026import com.scivicslab.pojoactor.core.Action;
027import com.scivicslab.pojoactor.core.ActionResult;
028import com.scivicslab.pojoactor.workflow.IIActorRef;
029import com.scivicslab.pojoactor.workflow.IIActorSystem;
030
031/**
032 * Actor reference for MultiplexerAccumulator.
033 *
034 * <p>This class wraps a {@link MultiplexerAccumulator} as an actor, allowing
035 * other actors to send messages to it via {@code callByActionName}. The actor
036 * forwards all output to its registered target accumulators (Console, File, Database).</p>
037 *
038 * <p>This class replaces the former {@code LoggingAccumulatorIIAR} with a more
039 * flexible architecture that supports multiple output destinations.</p>
040 *
041 * <h2>Supported Actions</h2>
042 * <ul>
043 *   <li>{@code add} - Adds output to all registered accumulators</li>
044 *   <li>{@code getSummary} - Returns formatted summary</li>
045 *   <li>{@code getCount} - Returns the number of added entries</li>
046 *   <li>{@code clear} - Clears all accumulated entries</li>
047 * </ul>
048 *
049 * <h2>Message Format for "add" Action</h2>
050 * <pre>{@code
051 * {
052 *   "source": "node-localhost",
053 *   "type": "stdout",
054 *   "data": "command output here"
055 * }
056 * }</pre>
057 *
058 * <h2>Usage</h2>
059 * <pre>{@code
060 * // Create and register the actor
061 * MultiplexerAccumulator multiplexer = new MultiplexerAccumulator();
062 * multiplexer.addTarget(new ConsoleAccumulator());
063 * multiplexer.addTarget(new FileAccumulator(logFilePath));
064 * multiplexer.addTarget(new DatabaseAccumulator(logStoreActor, dbExecutor, sessionId));
065 *
066 * MultiplexerAccumulatorIIAR actor = new MultiplexerAccumulatorIIAR(
067 *     "outputMultiplexer", multiplexer, system);
068 * system.addIIActor(actor);
069 *
070 * // Other actors can send messages by name
071 * IIActorRef<?> multiplexerActor = system.getIIActor("outputMultiplexer");
072 * multiplexerActor.callByActionName("add", jsonArg);
073 * }</pre>
074 *
075 * @author devteam@scivicslab.com
076 * @since 2.12.0
077 */
078public class MultiplexerAccumulatorIIAR extends IIActorRef<MultiplexerAccumulator> {
079
080    private final Logger logger;
081
082    /**
083     * Constructs a new MultiplexerAccumulatorIIAR.
084     *
085     * @param actorName the name of this actor (e.g., "outputMultiplexer")
086     * @param object the MultiplexerAccumulator instance
087     * @param system the actor system
088     */
089    public MultiplexerAccumulatorIIAR(String actorName, MultiplexerAccumulator object, IIActorSystem system) {
090        super(actorName, object, system);
091        this.logger = Logger.getLogger(actorName);
092    }
093
094    // ========================================================================
095    // Actions
096    // ========================================================================
097
098    /**
099     * Adds output to all registered accumulators.
100     *
101     * <p>Parses the JSON argument and forwards to the multiplexer, which then
102     * distributes to all registered target accumulators.</p>
103     *
104     * @param arg JSON object with source, type, and data fields
105     * @return ActionResult indicating success or failure
106     */
107    @Action("add")
108    public ActionResult add(String arg) {
109        // Note: Do NOT log here - it causes infinite loop via MultiplexerLogHandler
110        try {
111            JSONObject json = new JSONObject(arg);
112            String source = json.getString("source");
113            String type = json.getString("type");
114            String data = json.getString("data");
115
116            // Forward to multiplexer (which distributes to all targets)
117            this.tell(acc -> acc.add(source, type, data)).get();
118
119            return new ActionResult(true, "Added");
120        } catch (Exception e) {
121            logger.log(Level.SEVERE, "Error in add", e);
122            return new ActionResult(false, "Error: " + e.getMessage());
123        }
124    }
125
126    /**
127     * Returns formatted summary from all accumulators.
128     *
129     * @param arg not used
130     * @return ActionResult with the formatted summary
131     */
132    @Action("getSummary")
133    public ActionResult getSummary(String arg) {
134        try {
135            String summary = this.ask(MultiplexerAccumulator::getSummary).get();
136            return new ActionResult(true, summary);
137        } catch (ExecutionException | InterruptedException e) {
138            logger.log(Level.SEVERE, "Error in getSummary", e);
139            return new ActionResult(false, "Error: " + e.getMessage());
140        }
141    }
142
143    /**
144     * Returns the number of added entries.
145     *
146     * @param arg not used
147     * @return ActionResult with the count
148     */
149    @Action("getCount")
150    public ActionResult getCount(String arg) {
151        try {
152            int count = this.ask(MultiplexerAccumulator::getCount).get();
153            return new ActionResult(true, String.valueOf(count));
154        } catch (ExecutionException | InterruptedException e) {
155            logger.log(Level.SEVERE, "Error in getCount", e);
156            return new ActionResult(false, "Error: " + e.getMessage());
157        }
158    }
159
160    /**
161     * Clears all accumulated entries.
162     *
163     * @param arg not used
164     * @return ActionResult indicating success
165     */
166    @Action("clear")
167    public ActionResult clear(String arg) {
168        try {
169            this.tell(MultiplexerAccumulator::clear).get();
170            return new ActionResult(true, "Cleared");
171        } catch (ExecutionException | InterruptedException e) {
172            logger.log(Level.SEVERE, "Error in clear", e);
173            return new ActionResult(false, "Error: " + e.getMessage());
174        }
175    }
176}