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.iso; 020 021import java.io.EOFException; 022import java.io.IOException; 023import java.io.InterruptedIOException; 024import java.io.PrintStream; 025import java.lang.ref.WeakReference; 026import java.net.InetAddress; 027import java.net.InetSocketAddress; 028import java.net.ServerSocket; 029import java.net.Socket; 030import java.net.BindException; 031import java.net.SocketException; 032import java.net.UnknownHostException; 033import java.time.Duration; 034import java.util.*; 035import java.util.concurrent.ExecutorService; 036import java.util.concurrent.Semaphore; 037import java.util.concurrent.TimeUnit; 038import java.util.concurrent.atomic.AtomicInteger; 039import java.util.concurrent.locks.LockSupport; 040 041import org.jpos.core.Configurable; 042import org.jpos.core.Configuration; 043import org.jpos.core.ConfigurationException; 044import org.jpos.jfr.ChannelEvent; 045import org.jpos.log.AuditLogEvent; 046import org.jpos.log.evt.*; 047import org.jpos.q2.QFactory; 048import org.jpos.util.*; 049 050/** 051 * Accept ServerChannel sessions and forwards them to ISORequestListeners 052 * @author Alejandro P. Revilla 053 * @author Bharavi Gade 054 * @version $Revision$ $Date$ 055 */ 056@SuppressWarnings("unchecked") 057public class ISOServer extends Observable 058 implements LogSource, Runnable, Observer, ISOServerMBean, Configurable, 059 Loggeable, ISOServerSocketFactory 060{ 061 private enum PermLogPolicy { 062 ALLOW_NOLOG, DENY_LOG, ALLOW_LOG, DENY_LOGWARNING 063 } 064 065 int port; 066 private InetAddress bindAddr; 067 068 private Map<String,Boolean> specificIPPerms= new HashMap<>(); // TRUE means allow; FALSE means deny 069 private List<String> wildcardAllow; 070 private List<String> wildcardDeny; 071 private PermLogPolicy ipPermLogPolicy= PermLogPolicy.ALLOW_NOLOG; 072 073 protected ISOChannel clientSideChannel; 074 ISOPackager clientPackager; 075 protected Collection clientOutgoingFilters, clientIncomingFilters; 076 protected List<ISORequestListener> listeners; 077 public static final int DEFAULT_MAX_SESSIONS = 100; 078 public static final String LAST = ":last"; 079 String name; 080 protected long lastTxn = 0l; 081 protected Logger logger; 082 protected String realm; 083 protected String realmChannel; 084 protected ISOServerSocketFactory socketFactory = null; 085 086 private AtomicInteger connectionCount = new AtomicInteger(); 087 088 private int backlog; 089 protected Configuration cfg; 090 private volatile boolean shutdown = false; 091 private ServerSocket serverSocket; 092 private Map<String,WeakReference<ISOChannel>> channels; 093 protected boolean ignoreISOExceptions; 094 protected List<ISOServerEventListener> serverListeners = null; 095 private ExecutorService executor; 096 private Semaphore permits; 097 private int permitsCount = DEFAULT_MAX_SESSIONS; 098 private static final long SMALL_RELAX = 250; 099 private static final long LONG_RELAX = 5000; 100 private static final long SHUTDOWN_WAIT = 15000; 101 private final UUID uuid = UUID.randomUUID(); 102 103 /** 104 * @param port port to listen 105 * @param clientSide client side ISOChannel, used as a "clonable template" to accept new connections 106 */ 107 public ISOServer(int port, ServerChannel clientSide, int maxSessions) { 108 super(); 109 this.port = port; 110 this.clientSideChannel = clientSide; 111 this.clientPackager = clientSide.getPackager(); 112 if (clientSide instanceof FilteredChannel fc) { 113 this.clientOutgoingFilters = fc.getOutgoingFilters(); 114 this.clientIncomingFilters = fc.getIncomingFilters(); 115 } 116 listeners = new ArrayList<>(); 117 name = ""; 118 channels = Collections.synchronizedMap(new HashMap<>()); 119 serverListeners = Collections.synchronizedList(new ArrayList<>()); 120 121 if (maxSessions > 0) 122 permitsCount = maxSessions; 123 permits = new Semaphore(permitsCount); 124 } 125 126 @Override 127 public void setConfiguration (Configuration cfg) throws ConfigurationException { 128 this.cfg = cfg; 129 configureConnectionPerms(); 130 backlog = cfg.getInt ("backlog", 5); 131 ignoreISOExceptions = cfg.getBoolean("ignore-iso-exceptions"); 132 String ip = cfg.get ("bind-address", null); 133 if (ip != null) { 134 try { 135 bindAddr = InetAddress.getByName (ip); 136 } catch (UnknownHostException e) { 137 throw new ConfigurationException ("Invalid bind-address " + ip, e); 138 } 139 } 140 if (socketFactory == null) { 141 socketFactory = this; 142 } 143 if (socketFactory != this && socketFactory instanceof Configurable) { 144 ((Configurable)socketFactory).setConfiguration (cfg); 145 } 146 executor = QFactory.executorService(cfg.getBoolean("virtual-threads", false)); 147 } 148 149 // Helper method to setConfiguration. Handles "allow" and "deny" params 150 private void configureConnectionPerms() throws ConfigurationException 151 { 152 boolean hasAllows= false, hasDenies= false; 153 154 String[] allows= cfg.getAll ("allow"); 155 if (allows != null && allows.length > 0) { 156 hasAllows= true; 157 158 for (String allowIP : allows) { 159 allowIP= allowIP.trim(); 160 161 if (allowIP.indexOf('*') == -1) { // specific IP with no wildcards 162 specificIPPerms.put(allowIP, true); 163 } else { // there's a wildcard 164 wildcardAllow= (wildcardAllow == null) ? new ArrayList<>() : wildcardAllow; 165 String[] parts= allowIP.split("[*]"); 166 wildcardAllow.add(parts[0]); // keep only the first part 167 } 168 } 169 } 170 171 String[] denies= cfg.getAll ("deny"); 172 if (denies != null && denies.length > 0) { 173 hasDenies= true; 174 175 for (String denyIP : denies) { 176 boolean conflict= false; // used for a little sanity check 177 178 denyIP= denyIP.trim(); 179 if (denyIP.indexOf('*') == -1) { // specific IP with no wildcards 180 Boolean oldVal= specificIPPerms.put(denyIP, false); 181 conflict= (oldVal == Boolean.TRUE); 182 } else { // there's a wildcard 183 wildcardDeny= (wildcardDeny == null) ? new ArrayList<>() : wildcardDeny; 184 String[] parts= denyIP.split("[*]"); 185 if (wildcardAllow != null && wildcardAllow.contains(parts[0])) 186 conflict= true; 187 else 188 wildcardDeny.add(parts[0]); // keep only the first part 189 } 190 191 if (conflict) { 192 throw new ConfigurationException( 193 "Conflicting IP permission in '"+getName()+"' configuration: 'deny' " 194 +denyIP+" while having an identical previous 'allow'."); 195 } 196 } 197 } 198 199 // sum up permission policy and logging type 200 ipPermLogPolicy= (!hasAllows && !hasDenies) ? PermLogPolicy.ALLOW_NOLOG : // default when no permissions specified 201 ( hasAllows && !hasDenies) ? PermLogPolicy.DENY_LOG : 202 (!hasAllows && hasDenies) ? PermLogPolicy.ALLOW_LOG : 203 PermLogPolicy.DENY_LOGWARNING; // mixed allows & denies, if nothing matches we'll DENY and log a warning 204 } 205 206 /** 207 * add an ISORequestListener 208 * @param l request listener to be added 209 * @see ISORequestListener 210 */ 211 public void addISORequestListener(ISORequestListener l) { 212 listeners.add (l); 213 } 214 /** 215 * remove an ISORequestListener 216 * @param l a request listener to be removed 217 * @see ISORequestListener 218 */ 219 public void removeISORequestListener(ISORequestListener l) { 220 listeners.remove (l); 221 } 222 223 /** 224 * Shutdown this server 225 */ 226 public void shutdown () { 227 shutdown = true; 228 executor.submit(() -> { 229 Thread.currentThread().setName("ISOServer-shutdown"); 230 shutdownServer(); 231 if (!cfg.getBoolean("keep-channels")) { 232 shutdownChannels(); 233 } 234 }); 235 executor.shutdown(); 236 try { 237 if (!executor.awaitTermination(SHUTDOWN_WAIT, TimeUnit.MILLISECONDS)) { 238 executor.shutdownNow(); 239 } 240 } catch (InterruptedException e) { 241 Thread.currentThread().interrupt(); 242 } 243 } 244 private void shutdownServer () { 245 try { 246 if (serverSocket != null) { 247 serverSocket.close (); 248 fireEvent(new ISOServerShutdownEvent(this)); 249 } 250 } catch (IOException e) { 251 fireEvent(new ISOServerShutdownEvent(this)); 252 Logger.log (new LogEvent (this, "shutdown", e)); 253 } 254 } 255 private void shutdownChannels () { 256 Iterator iter = channels.entrySet().iterator(); 257 while (iter.hasNext()) { 258 Map.Entry entry = (Map.Entry) iter.next(); 259 WeakReference ref = (WeakReference) entry.getValue(); 260 ISOChannel c = (ISOChannel) ref.get (); 261 if (c != null) { 262 try { 263 c.disconnect (); 264 fireEvent(new ISOServerClientDisconnectEvent(this, c)); 265 } catch (IOException e) { 266 Logger.log (new LogEvent (this, "shutdown", e)); 267 } 268 } 269 } 270 } 271 private void purgeChannels() { 272 channels.entrySet().removeIf(entry -> { 273 ISOChannel channel = entry.getValue().get(); 274 return channel == null || !channel.isConnected(); 275 }); 276 } 277 278 @Override 279 public ServerSocket createServerSocket(int port) throws IOException { 280 ServerSocket ss = new ServerSocket(); 281 try { 282 ss.setReuseAddress(true); 283 ss.bind(new InetSocketAddress(bindAddr, port), backlog); 284 } catch(SecurityException e) { 285 ss.close(); 286 fireEvent(new ISOServerShutdownEvent(this)); 287 throw e; 288 } catch(IOException e) { 289 ss.close(); 290 fireEvent(new ISOServerShutdownEvent(this)); 291 throw e; 292 } 293 return ss; 294 } 295 296 //----------------------------------------------------------------------------- 297 // -- Helper Session inner class. It's a Runnable, running in its own 298 // -- thread and handling a connection to this ISOServer 299 // -- 300 protected Session createSession (ServerChannel channel) { 301 return new Session (channel); 302 } 303 304 protected class Session implements Runnable, LogSource { 305 ServerChannel channel; 306 String realm; 307 protected Session(ServerChannel channel) { 308 this.channel = channel; 309 realm = ISOServer.this.getRealm() + ".session"; 310 } 311 @Override 312 public void run() { 313 setChanged (); 314 notifyObservers (); 315 UUID sessionUUID = uuid; 316 String sessionInfo = ""; 317 if (channel instanceof BaseChannel baseChannel) { 318 Socket socket = baseChannel.getSocket (); 319 sessionInfo = socket.toString(); 320 sessionUUID = getSocketUUID(socket); 321 LogEvent ev = new LogEvent() 322 .withSource(this) 323 .withTraceId(sessionUUID) 324 .add(new SessionStart(getActiveConnections(), permitsCount, sessionInfo) 325 ); 326 if (!checkPermission (socket, ev)) 327 return; 328 realm = realm + "/" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort(); 329 } 330 try { 331 WeakReference<ISOChannel> wr = new WeakReference<> (channel); 332 channels.put (channel.getName(), wr); 333 channels.put (LAST, wr); // we are most likely the last one 334 while (true) try { 335 ISOMsg m = channel.receive(); 336 lastTxn = System.currentTimeMillis(); 337 for (ISORequestListener listener : listeners) { 338 if (listener.process(channel, m)) { 339 break; 340 } 341 } 342 } catch (ISOFilter.VetoException e) { 343 Logger.log(new LogEvent(this, "VetoException", e.getMessage())); 344 } catch (ISOException e) { 345 if (ignoreISOExceptions) { 346 Logger.log(new LogEvent(this, "ISOException", e.getMessage())); 347 } else { 348 throw e; 349 } 350 } 351 } catch (EOFException e) { 352 // Logger.log (new LogEvent (this, "session-warning", "<eof/>")); 353 } catch (SocketException e) { 354 if (!shutdown) 355 Logger.log (new LogEvent (this, "session-warning", e)); 356 } catch (InterruptedIOException e) { 357 // nothing to log 358 } catch (Throwable e) { 359 Logger.log (new LogEvent (this, "session-error", e)); 360 } 361 362 try { 363 channel.disconnect(); 364 fireEvent(new ISOServerClientDisconnectEvent(ISOServer.this, channel)); 365 } catch (IOException ex) { 366 Logger.log (new LogEvent (this, "session-error", ex)); 367 fireEvent(new ISOServerClientDisconnectEvent(ISOServer.this, channel)); 368 } 369 Logger.log(new LogEvent() 370 .withSource(this) 371 .withTraceId(sessionUUID) 372 .add(new SessionEnd(getActiveConnections(), permitsCount, sessionInfo) 373 ) 374 ); 375 } 376 @Override 377 public void setLogger (Logger logger, String realm) { 378 } 379 @Override 380 public String getRealm () { 381 return realm; 382 } 383 @Override 384 public Logger getLogger() { 385 return ISOServer.this.getLogger(); 386 } 387 388 private boolean checkPermission (Socket socket, LogEvent ev) { 389 try { 390 checkPermission0 (socket, ev); 391 return true; 392 } catch (ISOException e) { 393 try { 394 int delay = 1000 + new Random().nextInt(4000); 395 ev.addMessage(e.getMessage()); 396 ev.addMessage("delay=" + delay); 397 ISOUtil.sleep(delay); 398 socket.close(); 399 fireEvent(new ISOServerShutdownEvent(ISOServer.this)); 400 } catch (Throwable t) { 401 ev.addMessage (t); 402 } 403 } finally { 404 Logger.log (ev); 405 } 406 return false; 407 } 408 409 private void checkPermission0 (Socket socket, LogEvent evt) throws ISOException { 410 // if there are no allow/deny params, just return without doing any checks 411 // (i.e.: "silent allow policy", keeping backward compatibility) 412 if (specificIPPerms.isEmpty() && wildcardAllow == null && wildcardDeny == null) 413 return; 414 415 String ip= socket.getInetAddress().getHostAddress (); // The remote IP 416 417 // first, check allows or denies for specific/whole IPs (no wildcards) 418 boolean specificAllow = specificIPPerms.get(ip); 419 if (specificAllow == Boolean.TRUE) { // specific IP allow 420 evt.addMessage("access granted, ip=" + ip); 421 return; 422 } else if (specificAllow == Boolean.FALSE) { // specific IP deny 423 throw new ISOException("access denied, ip=" + ip); 424 } else { // no specific match under the specificIPPerms Map 425 // We check the wildcard lists, deny first 426 if (wildcardDeny != null) { 427 for (String wdeny : wildcardDeny) { 428 if (ip.startsWith(wdeny)) { 429 throw new ISOException ("access denied, ip=" + ip); 430 } 431 } 432 } 433 if (wildcardAllow != null) { 434 for (String wallow : wildcardAllow) { 435 if (ip.startsWith(wallow)) { 436 evt.addMessage("access granted, ip=" + ip); 437 return; 438 } 439 } 440 } 441 442 // Reaching this point means that nothing matched our specific or wildcard rules, so we fall 443 // back on the default permission policies and log type 444 switch (ipPermLogPolicy) { 445 case DENY_LOG: // only allows were specified, default policy is to deny non-matches and log the issue 446 throw new ISOException ("access denied, ip=" + ip); 447 // break; 448 449 case ALLOW_LOG: // only denies were specified, default policy is to allow non-matches and log the issue 450 evt.addMessage("access granted, ip=" + ip); 451 break; 452 453 case DENY_LOGWARNING: // mix of allows and denies were specified, but the IP matched no rules! 454 // so we adopt a deny policy but give a special warning 455 throw new ISOException ("access denied, ip=" + ip + " (WARNING: the IP did not match any rules!)"); 456 // break; 457 458 case ALLOW_NOLOG: // this is the default case when no allow/deny are specified 459 // the method will abort early on the first "if", so this is here just for completion 460 break; 461 } 462 463 } 464 // we should never reach this point!! :-) 465 } 466 } // inner class Session 467 468 //------------------------------------------------------------------------------- 469 //-- This is the main run for this ISOServer's Thread 470 @Override 471 public void run() { 472 if (socketFactory == null) { 473 socketFactory = this; 474 } 475 int round = 0; 476 serverLoop : while (!shutdown) { 477 round++; 478 try { 479 if (permits.availablePermits() <= 0) { 480 LockSupport.parkNanos(Duration.ofMillis(SMALL_RELAX).toNanos()); 481 if (round % 240 == 0 && cfg.getBoolean("permits-exhaustion-warning", true)) { 482 log(new Warning("permits exhausted " + serverSocket.toString())); 483 } 484 continue; 485 } 486 serverSocket = socketFactory.createServerSocket(port); 487 log (new Listen(port, bindAddr, permits.availablePermits(), backlog)); 488 while (!shutdown) { 489 try { 490 if (permits.availablePermits() <= 0) { 491 ChannelEvent jfr = new ChannelEvent.AcceptException( 492 "Available permits too low (%d)".formatted(permits.availablePermits()) 493 ); 494 jfr.begin(); 495 try { 496 serverSocket.close(); 497 fireEvent(new ISOServerShutdownEvent(this)); 498 } catch (IOException e){ 499 log (new ThrowableAuditLogEvent(e)); 500 } finally { 501 jfr.commit(); 502 } 503 continue serverLoop; 504 } 505 final ServerChannel channel = (ServerChannel) clientSideChannel.clone(); 506 channel.accept (serverSocket); 507 if (connectionCount.getAndIncrement() % 100 == 0) { 508 purgeChannels (); 509 } 510 executor.submit (() -> { 511 try { 512 permits.acquireUninterruptibly(); 513 createSession(channel).run(); 514 } finally { 515 permits.release(); 516 } 517 }); 518 setChanged (); 519 notifyObservers (this); 520 fireEvent(new ISOServerAcceptEvent(this, channel)); 521 if (channel instanceof Observable) { 522 ((Observable)channel).addObserver (this); 523 } 524 } catch (SocketException e) { 525 if (!shutdown) { 526 log (new ThrowableAuditLogEvent(e)); 527 relax(); 528 continue serverLoop; 529 } 530 } catch (IOException e) { 531 log (new ThrowableAuditLogEvent(e)); 532 relax(); 533 } 534 } // while !shutdown 535 } catch (BindException e) { 536 warn(new Listen(port, bindAddr, 537 permits.availablePermits(), backlog, 538 "(round "+round+") "+e)); 539 relax(); 540 } catch (Throwable e) { 541 log (new ThrowableAuditLogEvent(e)); 542 relax(); 543 } 544 } 545 } // ISOServer's run() 546 //------------------------------------------------------------------------------- 547 548 private void relax() { 549 LockSupport.parkNanos(Duration.ofMillis(LONG_RELAX).toNanos()); 550 } 551 552 /** 553 * associates this ISOServer with a name using NameRegistrar 554 * @param name name to register 555 * @see NameRegistrar 556 */ 557 public void setName (String name) { 558 this.name = name; 559 NameRegistrar.register ("server."+name, this); 560 } 561 /** 562 * @return ISOServer instance with given name. 563 * @throws NameRegistrar.NotFoundException; 564 * @see NameRegistrar 565 */ 566 public static ISOServer getServer (String name) 567 throws NameRegistrar.NotFoundException 568 { 569 return NameRegistrar.get ("server."+name); 570 } 571 /** 572 * @return this ISOServer's name ("" if no name was set) 573 */ 574 public String getName() { 575 return this.name; 576 } 577 @Override 578 public void setLogger (Logger logger, String realm) { 579 this.logger = logger; 580 this.realm = realm; 581 this.realmChannel = realm + ".channel"; 582 } 583 @Override 584 public String getRealm () { 585 return realm; 586 } 587 @Override 588 public Logger getLogger() { 589 return logger; 590 } 591 @Override 592 public void update(Observable o, Object arg) { 593 setChanged (); 594 notifyObservers (arg); 595 } 596 /** 597 * Gets the ISOClientSocketFactory (may be null) 598 * @see ISOClientSocketFactory 599 * @since 1.3.3 600 */ 601 public ISOServerSocketFactory getSocketFactory() { 602 return socketFactory; 603 } 604 /** 605 * Sets the specified Socket Factory to create sockets 606 * @param socketFactory the ISOClientSocketFactory 607 * @see ISOClientSocketFactory 608 * @since 1.3.3 609 */ 610 public void setSocketFactory(ISOServerSocketFactory socketFactory) { 611 this.socketFactory = socketFactory; 612 } 613 @Override 614 public int getPort () { 615 return port; 616 } 617 @Override 618 public void resetCounters () { 619 connectionCount.set(0); 620 lastTxn = 0l; 621 } 622 623 /** 624 * @return number of connections accepted by this server 625 */ 626 @Override 627 public int getConnectionCount () { 628 return connectionCount.get(); 629 } 630 631 /** 632 * @return most recently connected ISOChannel or null 633 */ 634 public ISOChannel getLastConnectedISOChannel () { 635 return getISOChannel (LAST); 636 } 637 638 /** 639 * @return ISOChannel under the given name 640 */ 641 public ISOChannel getISOChannel (String name) { 642 WeakReference ref = channels.get (name); 643 if (ref != null) { 644 return (ISOChannel) ref.get (); 645 } 646 return null; 647 } 648 649 650 @Override 651 public String getISOChannelNames () { 652 StringBuilder sb = new StringBuilder (); 653 Iterator iter = channels.entrySet().iterator(); 654 for (int i=0; iter.hasNext(); i++) { 655 Map.Entry entry = (Map.Entry) iter.next(); 656 WeakReference ref = (WeakReference) entry.getValue(); 657 ISOChannel c = (ISOChannel) ref.get (); 658 if (c != null && !LAST.equals (entry.getKey()) && c.isConnected()) { 659 if (i > 0 && !sb.isEmpty()) { 660 sb.append (' '); 661 } 662 sb.append (entry.getKey()); 663 } 664 } 665 return sb.toString(); 666 } 667 public String getCountersAsString () { 668 StringBuilder sb = new StringBuilder (); 669 int cnt[] = getCounters(); 670 sb.append ("connected="); 671 sb.append (cnt[2]); 672 sb.append (", rx="); 673 sb.append (cnt[0]); 674 sb.append (", tx="); 675 sb.append (cnt[1]); 676 sb.append (", last="); 677 sb.append (lastTxn); 678 if (lastTxn > 0) { 679 sb.append (", idle="); 680 sb.append(System.currentTimeMillis() - lastTxn); 681 sb.append ("ms"); 682 } 683 return sb.toString(); 684 } 685 686 public int[] getCounters() 687 { 688 Iterator iter = channels.entrySet().iterator(); 689 int[] cnt = new int[3]; 690 cnt[2] = 0; 691 for (int i=0; iter.hasNext(); i++) { 692 Map.Entry entry = (Map.Entry) iter.next(); 693 WeakReference ref = (WeakReference) entry.getValue(); 694 ISOChannel c = (ISOChannel) ref.get (); 695 if (c != null && !LAST.equals (entry.getKey()) && c.isConnected()) { 696 cnt[2]++; 697 if (c instanceof BaseChannel) { 698 int[] cc = ((BaseChannel)c).getCounters(); 699 cnt[0] += cc[ISOChannel.RX]; 700 cnt[1] += cc[ISOChannel.TX]; 701 } 702 } 703 } 704 return cnt; 705 } 706 707 @Override 708 public int getTXCounter() { 709 int cnt[] = getCounters(); 710 return cnt[1]; 711 } 712 @Override 713 public int getRXCounter() { 714 int cnt[] = getCounters(); 715 return cnt[0]; 716 } 717 public int getConnections () { 718 return connectionCount.get(); 719 } 720 @Override 721 public long getLastTxnTimestampInMillis() { 722 return lastTxn; 723 } 724 @Override 725 public long getIdleTimeInMillis() { 726 return lastTxn > 0L ? System.currentTimeMillis() - lastTxn : -1L; 727 } 728 729 730 @Override 731 public String getCountersAsString (String isoChannelName) { 732 ISOChannel channel = getISOChannel(isoChannelName); 733 StringBuffer sb = new StringBuffer(); 734 if (channel instanceof BaseChannel) { 735 int[] counters = ((BaseChannel)channel).getCounters(); 736 append (sb, "rx=", counters[ISOChannel.RX]); 737 append (sb, ", tx=", counters[ISOChannel.TX]); 738 append (sb, ", connects=", counters[ISOChannel.CONNECT]); 739 } 740 return sb.toString(); 741 } 742 @Override 743 public void dump (PrintStream p, String indent) { 744 p.println (indent + getCountersAsString()); 745 Iterator iter = channels.entrySet().iterator(); 746 String inner = indent + " "; 747 for (int i=0; iter.hasNext(); i++) { 748 Map.Entry entry = (Map.Entry) iter.next(); 749 WeakReference ref = (WeakReference) entry.getValue(); 750 ISOChannel c = (ISOChannel) ref.get (); 751 if (c != null && !LAST.equals (entry.getKey()) && c.isConnected() && c instanceof BaseChannel) { 752 StringBuilder sb = new StringBuilder (); 753 int[] cc = ((BaseChannel)c).getCounters(); 754 sb.append (inner); 755 sb.append (entry.getKey()); 756 sb.append (": rx="); 757 sb.append (cc[ISOChannel.RX]); 758 sb.append (", tx="); 759 sb.append (cc[ISOChannel.TX]); 760 sb.append (", last="); 761 sb.append (lastTxn); 762 p.println (sb); 763 } 764 } 765 } 766 private void append (StringBuffer sb, String name, int value) { 767 sb.append (name); 768 sb.append (value); 769 } 770 771 public void addServerEventListener(ISOServerEventListener listener) { 772 serverListeners.add(listener); 773 } 774 public void removeServerEventListener(ISOServerEventListener listener) { 775 serverListeners.remove(listener); 776 } 777 778 public void fireEvent(EventObject event) { 779 for (ISOServerEventListener l : serverListeners) { 780 try { 781 l.handleISOServerEvent(event); 782 } 783 catch (Throwable ignore) { 784 /* 785 * Don't want an exception from a handler to exit the loop or 786 * let it bubble up. 787 * If it bubbles up it can cause side effects like getting caught 788 * in the throwable catch leading to server trying to listen on 789 * the same port. 790 * We don't want a side effect in jpos caused by custom user 791 * handler code. 792 */ 793 } 794 795 } 796 } 797 798 private void warn (AuditLogEvent log) { 799 Logger.log(new LogEvent(Log.WARN) 800 .withSource(this) 801 .withTraceId(uuid) 802 .add(log) 803 ); 804 } 805 806 private void log (AuditLogEvent log) { 807 Logger.log(new LogEvent() 808 .withSource(this) 809 .withTraceId(uuid) 810 .add(log) 811 ); 812 } 813 814 private void log (String level, String message) { 815 LogEvent evt = new LogEvent (this, level).withTraceId(uuid); 816 evt.addMessage (message); 817 Logger.log (evt); 818 } 819 820 public int getActiveConnections () { 821 return permitsCount - permits.availablePermits(); 822 } 823 824 private UUID getSocketUUID(Socket socket) { 825 return socket != null ? 826 new UUID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits() ^ socket.hashCode()) : 827 uuid; 828 } 829}