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}