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.transaction.participant; 020 021import java.io.Serializable; 022 023import org.jpos.core.Configurable; 024import org.jpos.core.Configuration; 025import org.jpos.core.ConfigurationException; 026import org.jpos.core.annotation.Config; 027import org.jpos.iso.ISOMsg; 028import org.jpos.iso.ISOSource; 029import org.jpos.space.LocalSpace; 030import org.jpos.space.SpaceSource; 031import org.jpos.transaction.Context; 032import org.jpos.transaction.AbortParticipant; 033import org.jpos.transaction.TransactionManager; 034 035import static org.jpos.transaction.ContextConstants.*; 036 037/** 038 * Transaction participant that sends the response message stored in the 039 * {@link Context} back over the originating {@link ISOSource}, with optional 040 * header handling driven by {@link HeaderStrategy}. 041 */ 042@SuppressWarnings("unused") 043public class SendResponse implements AbortParticipant, Configurable { 044 /** Default constructor; no instance state to initialise. */ 045 public SendResponse() {} 046 private String source; 047 private String request; 048 private String response; 049 private LocalSpace isp; 050 private long timeout = 70000L; 051 private HeaderStrategy headerStrategy; 052 053 @Config("abort-on-closed") 054 private boolean abortOnClosed = true; 055 056 public int prepare (long id, Serializable context) { 057 Context ctx = (Context) context; 058 ISOSource source = ctx.get (this.source); 059 if (abortOnClosed && (source == null || !source.isConnected())) { 060 ctx.log (this.source + " not present or not longer connected"); 061 return ABORTED | READONLY | NO_JOIN; 062 } 063 064 return PREPARED | READONLY; 065 } 066 067 public void commit (long id, Serializable context) { 068 sendResponse(id, (Context) context); 069 } 070 public void abort (long id, Serializable context) { 071 sendResponse(id, (Context) context); 072 } 073 074 private void sendResponse (long id, Context ctx) { 075 ISOSource src = ctx.get (source); 076 ISOMsg m = ctx.get(request); 077 ISOMsg resp = ctx.get (response); 078 try { 079 if (ctx.getResult().hasInhibit()) { 080 ctx.log("*** RESPONSE INHIBITED ***"); 081 } else if (ctx.get (TX.toString()) != null) { 082 ctx.log("*** PANIC - TX not null - RESPONSE OMITTED ***"); 083 } else if (resp == null) { 084 ctx.log (response + " not present"); 085 } else if (src == null) { 086 ctx.log (source + " not present"); 087 } else if (!src.isConnected()) 088 ctx.log (source + " is no longer connected"); 089 else { 090 if (src instanceof SpaceSource) 091 ((SpaceSource)src).init(isp, timeout); 092 if (src.isConnected() && resp != null) { 093 headerStrategy.handleHeader(m, resp); 094 src.send(resp); 095 } 096 } 097 } catch (Throwable t) { 098 ctx.log(t); 099 } 100 } 101 102 @Override 103 public void setConfiguration(Configuration cfg) throws ConfigurationException { 104 source = cfg.get ("source", SOURCE.toString()); 105 request = cfg.get ("request", REQUEST.toString()); 106 response = cfg.get ("response", RESPONSE.toString()); 107 timeout = cfg.getLong ("timeout", timeout); 108 try { 109 headerStrategy = HeaderStrategy.valueOf( 110 cfg.get("header-strategy", "PRESERVE_RESPONSE").toUpperCase() 111 ); 112 } catch (IllegalArgumentException e) { 113 throw new ConfigurationException (e.getMessage()); 114 } 115 } 116 /** 117 * Captures the input space from the hosting transaction manager so the 118 * participant can re-arm a {@link SpaceSource} during commit. 119 * 120 * @param tm the hosting transaction manager 121 */ 122 public void setTransactionManager(TransactionManager tm) { 123 isp = (LocalSpace) tm.getInputSpace(); 124 } 125 126 /** Strategy contract for populating the response message's ISO header. */ 127 private interface HeaderHandler { 128 /** 129 * Adjusts {@code r}'s header based on the request {@code m}. 130 * 131 * @param m original request message 132 * @param r response message about to be sent 133 */ 134 void handleHeader (ISOMsg m, ISOMsg r); 135 } 136 137 /** Selects how the response message's ISO header is populated before sending. */ 138 @SuppressWarnings("unused") 139 public enum HeaderStrategy implements HeaderHandler { 140 /** Copies the request header onto the response. */ 141 PRESERVE_ORIGINAL() { 142 @Override 143 public void handleHeader(ISOMsg m, ISOMsg r) { 144 r.setHeader(m.getHeader()); 145 } 146 }, 147 /** Leaves whatever header the response already carries (default). */ 148 PRESERVE_RESPONSE() { 149 @Override 150 public void handleHeader(ISOMsg m, ISOMsg r) { } 151 }, 152 /** Clears the response header so the channel emits no header bytes. */ 153 SET_TO_NULL() { 154 @Override 155 public void handleHeader(ISOMsg m, ISOMsg r) { 156 r.setHeader((byte[]) null); 157 } 158 } 159 } 160}