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.iso.*; 027import org.jpos.rc.CMF; 028import org.jpos.rc.Result; 029import org.jpos.transaction.ContextConstants; 030import org.jpos.transaction.TransactionParticipant; 031import org.jpos.util.Caller; 032import org.jpos.util.Chronometer; 033import org.jpos.util.NameRegistrar; 034import org.jpos.transaction.Context; 035 036/** 037 * Transaction participant that routes the request to a downstream MUX, 038 * waits for the response (synchronously or via continuations), and stores 039 * the response back into the {@link Context}. 040 */ 041public class QueryHost implements TransactionParticipant, Configurable { 042 /** Default {@link Context} key used to override the per-transaction timeout. */ 043 public static final String TIMEOUT_NAME = "QUERYHOST_TIMEOUT"; 044 045 private static final long DEFAULT_TIMEOUT = 30000L; 046 private static final long DEFAULT_WAIT_TIMEOUT = 1000L; 047 048 private long timeout; 049 private long waitTimeout; 050 private String timeoutName = TIMEOUT_NAME; // default ctx name 051 private String requestName; 052 private String responseName; 053 private String destination; 054 private boolean continuations; 055 private Configuration cfg; 056 private boolean ignoreUnreachable; 057 private boolean checkConnected= true; 058 059 /** Default constructor. */ 060 public QueryHost () { 061 super(); 062 } 063 public int prepare (long id, Serializable ser) { 064 Context ctx = (Context) ser; 065 066 Result result = ctx.getResult(); 067 String ds = ctx.getString(destination); 068 if (ds == null) { 069 return result.fail( 070 CMF.MISCONFIGURED_ENDPOINT, Caller.info(), "'%s' not present in Context", destination 071 ).FAIL(); 072 } 073 String muxName = cfg.get ("mux." + ds , "mux." + ds); 074 MUX mux = NameRegistrar.getIfExists (muxName); 075 if (mux == null) 076 return result.fail(CMF.MISCONFIGURED_ENDPOINT, Caller.info(), "MUX '%s' not found", muxName).FAIL(); 077 078 ISOMsg m = ctx.get (requestName); 079 if (m == null) 080 return result.fail(CMF.INVALID_REQUEST, Caller.info(), "'%s' is null", requestName).FAIL(); 081 082 Chronometer chronometer = new Chronometer(); 083 if (isConnected(mux)) { 084 long t = Math.max(resolveTimeout(ctx) - chronometer.elapsed(), 1000L); // give at least a second to catch a response 085 try { 086 ISOMsg resp = mux.request(m, t); 087 if (resp != null) { 088 ctx.put(responseName, resp); 089 return PREPARED | READONLY | NO_JOIN; 090 } else if (ignoreUnreachable) { 091 ctx.log(String.format ("MUX '%s' no response", muxName)); 092 } else { 093 return result.fail(CMF.HOST_UNREACHABLE, Caller.info(), "'%s' does not respond", muxName).FAIL(); 094 } 095 } catch (ISOException e) { 096 return result.fail(CMF.SYSTEM_ERROR, Caller.info(), e.getMessage()).FAIL(); 097 } 098 } else if (ignoreUnreachable) { 099 ctx.log(String.format ("MUX '%s' not connected", muxName)); 100 } else { 101 return result.fail(CMF.HOST_UNREACHABLE, Caller.info(), "'%s' is not connected", muxName).FAIL(); 102 } 103 return PREPARED | NO_JOIN | READONLY; 104 } 105 106 public void setConfiguration (Configuration cfg) throws ConfigurationException { 107 this.cfg = cfg; 108 timeout = cfg.getLong ("timeout", DEFAULT_TIMEOUT); 109 waitTimeout = cfg.getLong ("wait-timeout", DEFAULT_WAIT_TIMEOUT); 110 timeoutName = cfg.get("timeout-name", timeoutName); 111 requestName = cfg.get ("request", ContextConstants.REQUEST.toString()); 112 responseName = cfg.get ("response", ContextConstants.RESPONSE.toString()); 113 destination = cfg.get ("destination", ContextConstants.DESTINATION.toString()); 114 ignoreUnreachable = cfg.getBoolean("ignore-host-unreachable", false); 115 checkConnected = cfg.getBoolean("check-connected", checkConnected); 116 } 117 118 /** 119 * Resolves the per-transaction request timeout, honouring any value the 120 * upstream code stashed in the context under {@link #TIMEOUT_NAME}. 121 * 122 * @param ctx transaction context 123 * @return effective timeout in milliseconds 124 */ 125 protected long resolveTimeout(Context ctx) { 126 Object o = ctx.get(timeoutName); 127 if (o == null) 128 return timeout; 129 else if (o instanceof Number) 130 return ((Number)o).longValue(); 131 else 132 return Long.parseLong(o.toString()); 133 } 134 135 /** 136 * Indicates whether {@code mux} is currently connected, honouring the 137 * {@code check-connected} configuration switch. 138 * 139 * @param mux MUX selected for this transaction 140 * @return {@code true} if connectivity checks are disabled or the MUX reports connected 141 */ 142 protected boolean isConnected (MUX mux) { 143 if (!checkConnected || mux.isConnected()) 144 return true; 145 long timeout = System.currentTimeMillis() + waitTimeout; 146 while (System.currentTimeMillis() < timeout) { 147 if (mux.isConnected()) 148 return true; 149 ISOUtil.sleep (500); 150 } 151 return false; 152 } 153}