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.rc;
020
021import org.jpos.transaction.TransactionConstants;
022import org.jpos.util.Loggeable;
023
024import java.io.PrintStream;
025import java.util.*;
026import java.util.stream.Collectors;
027
028/**
029 * Represents the Result of a transaction
030 */
031public class Result implements Loggeable {
032    private final List<Entry> entries = Collections.synchronizedList(new ArrayList<>());
033
034    public Result() {
035        super();
036    }
037
038    public Result info (String source, String format, Object ... args) {
039        return add(Type.INFO, null, source, format, args);
040    }
041    public Result warn (String source, String format, Object ... args) {
042        return add(Type.WARN, null, source, format, args);
043    }
044    public Result success (IRC irc, String source, String format, Object ... args) {
045        if (!irc.success())
046            throw new IllegalArgumentException("Invalid success IRC " + irc);
047        return add(Type.SUCCESS, irc, source, ""+format, args);
048    }
049    public Result fail (IRC irc, String source, String format, Object ... args) {
050        synchronized (entries) {
051            if (isSuccess()) {
052                format = "" + format + " (inhibits " + success() + ")";
053            }
054            return add(Type.FAIL, irc, source, ""+format, args);
055        }
056    }
057
058    /**
059     * Helper method used to avoid adding an extra 'return' line in failing transaction participants
060     * @return TransactionConstants.FAIL which is basically ABORT | READONLY | NO_JOIN;
061     */
062    public int FAIL() {
063        return TransactionConstants.FAIL;
064    }
065    public boolean hasInfo() {
066        synchronized (entries) {
067            return entries.stream().anyMatch(e -> e.type == Type.INFO);
068        }
069    }
070    public boolean hasWarnings() {
071        synchronized (entries) {
072            return entries.stream().anyMatch(e -> e.type == Type.WARN);
073        }
074    }
075    public boolean hasFailures() {
076        synchronized (entries) {
077            return entries.stream().anyMatch(e -> e.type == Type.FAIL);
078        }
079    }
080    public boolean hasInhibit() {
081        synchronized (entries) {
082            return entries.stream().anyMatch(e -> e.irc != null && e.irc.inhibit());
083        }
084    }
085
086    public boolean hasIRC (IRC irc) {
087        synchronized (entries) {
088            return entries.stream().anyMatch(entry -> entry.irc == irc);
089        }
090    }
091    public boolean hasFailure(IRC irc) {
092        synchronized (entries) {
093            return failureList().stream().anyMatch(entry -> entry.irc == irc);
094        }
095    }
096    public boolean hasWarning(IRC irc) {
097        synchronized (entries) {
098            return warningList().stream().anyMatch(entry -> entry.irc == irc);
099        }
100    }
101    public boolean hasInfo(IRC irc) {
102        synchronized (entries) {
103            return infoList().stream().anyMatch(entry -> entry.irc == irc);
104        }
105    }
106
107    public boolean isSuccess() {
108        synchronized (entries) {
109            return isSuccess0() && !hasFailures();
110        }
111    }
112    public Entry failure() {
113        synchronized (entries) {
114            Optional<Entry> entry = entries.stream().filter(e -> e.type == Type.FAIL).findFirst();
115            return entry.isPresent() ? entry.get() : null;
116        }
117    }
118    public Entry success() {
119        synchronized (entries) {
120            Optional<Entry> entry = entries.stream().filter(e -> e.type == Type.SUCCESS).findFirst();
121            return entry.isPresent() && !hasFailures() ? entry.get() : null;
122        }
123    }
124    public List<Entry> entries() {
125        return entries;
126    }
127
128    public List<Entry> infoList() {
129        return entries
130          .stream()
131          .filter(s -> s.type == Type.INFO)
132          .collect(Collectors.toList());
133    }
134
135    public List<Entry> successList() {
136        return entries
137          .stream()
138          .filter(s -> s.type == Type.SUCCESS)
139          .collect(Collectors.toList());
140    }
141
142
143    public List<Entry> warningList() {
144        return entries
145          .stream()
146          .filter(s -> s.type == Type.WARN)
147          .collect(Collectors.toList());
148    }
149    public List<Entry> failureList() {
150        return entries
151          .stream()
152          .filter(s -> s.type == Type.FAIL)
153          .collect(Collectors.toList());
154    }
155    private Result add (Type type, IRC irc, String source, String format, Object ... args) {
156        entries.add (
157          new Entry(type, irc, source, String.format(format, args))
158        );
159        return this;
160    }
161
162    @Override
163    public void dump(final PrintStream ps, final String indent) {
164        if (entries.size() == 0) {
165            ps.printf ("%s<result/>%n", indent);
166            return;
167        }
168        final String inner = indent + "  ";
169        ps.printf("%s<result>%n", indent);
170        synchronized (entries) {
171            if (isSuccess0()) {
172                String inhibited = hasFailures() ? " inhibited='true'" : "";
173                ps.printf("%s<success%s>%n", inner, inhibited);
174                entries
175                  .stream()
176                  .filter(s -> s.type == Type.SUCCESS)
177                  .forEach(e -> ps.printf("%s  [%s] %s %s%n", inner, e.irc, e.source, e.message));
178                ps.printf("%s</success>%n", inner);
179            }
180            if (hasFailures()) {
181                ps.printf("%s<fail>%n", inner);
182                entries
183                  .stream()
184                  .filter(s -> s.type == Type.FAIL)
185                  .forEach(e -> ps.printf("%s  [%s] %s %s%n", inner, e.irc, e.source, e.message));
186                ps.printf("%s</fail>%n", inner);
187            }
188            if (hasWarnings()) {
189                ps.printf("%s<warn>%n", inner);
190                entries
191                  .stream()
192                  .filter(s -> s.type == Type.WARN)
193                  .forEach(e -> ps.printf("%s  [%s] %s%n", inner, e.source, e.message));
194                ps.printf("%s</warn>%n", inner);
195            }
196            if (hasInfo()) {
197                ps.printf("%s<info>%n", inner);
198                entries
199                  .stream()
200                  .filter(s -> s.type == Type.INFO)
201                  .forEach(e -> ps.printf("%s  [%s] %s%n", inner, e.source, e.message));
202                ps.printf("%s</info>%n", inner);
203            }
204        }
205        ps.printf("%s</result>%n", indent);
206    }
207
208    @Override
209    public String toString() {
210        return "Result{" +
211          "entries=" + entries +
212          '}';
213    }
214
215    private boolean isSuccess0() {
216        synchronized (entries) {
217            return entries.stream().anyMatch(e -> e.type == Type.SUCCESS);
218        }
219    }
220
221    private enum Type {
222        INFO,
223        WARN,
224        SUCCESS,
225        FAIL
226    }
227    public static class Entry {
228        Type type;
229        IRC irc;
230        String source;
231        String message;
232
233        public Entry(Type type, IRC irc, String source, String message) {
234            this.type = type;
235            this.irc = irc;
236            this.source = source;
237            this.message = message;
238        }
239
240        public Type getType() {
241            return type;
242        }
243
244        public IRC getIrc() {
245            return irc;
246        }
247
248        public String getSource() {
249            return source;
250        }
251
252        public String getMessage() {
253            return message;
254        }
255
256        @Override
257        public String toString() {
258            return "Entry{" +
259              "type=" + type +
260              ", irc=" + irc +
261              ", source='" + source + '\'' +
262              ", message='" + message + '\'' +
263              '}';
264        }
265    }
266}