/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.invocation.jrmp.interfaces; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.rmi.ServerException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import javax.transaction.SystemException; import javax.transaction.TransactionRolledbackException; import org.jboss.ha.framework.interfaces.ClusteringTargetsRepository; import org.jboss.ha.framework.interfaces.FamilyClusterInfo; import org.jboss.ha.framework.interfaces.GenericClusteringException; import org.jboss.ha.framework.interfaces.HARMIResponse; import org.jboss.ha.framework.interfaces.LoadBalancePolicy; import org.jboss.invocation.Invocation; import org.jboss.invocation.Invoker; import org.jboss.invocation.InvokerInterceptor; import org.jboss.invocation.InvokerProxyHA; import org.jboss.invocation.MarshalledInvocation; import org.jboss.invocation.PayloadKey; import org.jboss.invocation.ServiceUnavailableException; import org.jboss.logging.Logger; /** * An extension of the JRMPInvokerProxy that supports failover and load * balancing among a * * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a> * @author Scott.Stark@jboss.org * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a> * @version $Revision: 83230 $ */ public class JRMPInvokerProxyHA extends JRMPInvokerProxy implements InvokerProxyHA, Externalizable { // Public -------------------------------------------------------- /** The serialVersionUID * @since 1.7.2.8 */ private static final long serialVersionUID = -967671822225981666L; private static final Logger log = Logger.getLogger(JRMPInvokerProxyHA.class); public static final HashSet colocation = new HashSet(); public static final Map txFailoverAuthorizations = Collections.synchronizedMap(new WeakHashMap()); protected LoadBalancePolicy loadBalancePolicy; protected String proxyFamilyName = null; FamilyClusterInfo familyClusterInfo = null; //protected transient long currentViewId = 0; /** Trace level logging flag only set when the proxy is created or read from JNDI */ protected transient boolean trace = false; public JRMPInvokerProxyHA() {} public JRMPInvokerProxyHA(List targets, LoadBalancePolicy policy, String proxyFamilyName, long viewId) { this.familyClusterInfo = ClusteringTargetsRepository.initTarget (proxyFamilyName, targets, viewId); this.loadBalancePolicy = policy; this.proxyFamilyName = proxyFamilyName; this.trace = log.isTraceEnabled(); if( trace ) log.trace("Init, cluterInfo: "+familyClusterInfo+", policy="+loadBalancePolicy); } public String getProxyFamilyName() { return proxyFamilyName; } public void updateClusterInfo (ArrayList targets, long viewId) { if (familyClusterInfo != null) this.familyClusterInfo.updateClusterInfo (targets, viewId); } public FamilyClusterInfo getFamilyClusterInfo() { return familyClusterInfo; } public void forbidTransactionFailover(Object tpc) { txFailoverAuthorizations.put(tpc, null); } public Object getRemoteTarget() { return getRemoteTarget(null); } public Object getRemoteTarget(Invocation invocationBasedRouting) { return loadBalancePolicy.chooseTarget(this.familyClusterInfo, invocationBasedRouting); } public void remoteTargetHasFailed(Object target) { removeDeadTarget(target); } protected void removeDeadTarget(Object target) { //System.out.println("Removing a dead target: Size before : " + Integer.toString(this.familyClusterInfo.getTargets ().size())); if (this.familyClusterInfo != null) this.familyClusterInfo.removeDeadTarget (target); } protected int totalNumberOfTargets () { if (this.familyClusterInfo != null) return this.familyClusterInfo.getTargets ().size (); else return 0; } protected void resetView () { this.familyClusterInfo.resetView (); } /** * Returns wether we are local to the originating container or not. */ public boolean isLocal(Invocation invocation) { return colocation.contains(invocation.getObjectName()); } public boolean txContextAllowsFailover (Invocation invocation) { Object tpc = getTransactionPropagationContext(); if (tpc != null) { if (trace) { log.trace("Checking tx failover authorisation map with tpc " + tpc); } /* If the map contains the tpc, then we can't allow a failover */ return ! txFailoverAuthorizations.containsKey(tpc); } return true; } public void invocationHasReachedAServer (Invocation invocation) { Object tpc = getTransactionPropagationContext(); if (tpc != null) { forbidTransactionFailover(tpc); } } /** * The invocation on the delegate, calls the right invoker. Remote if we are remote, local if we * are local. */ public Object invoke(Invocation invocation) throws Exception { // we give the opportunity, to any server interceptor, to know if this a // first invocation to a node or if it is a failovered call // int failoverCounter = 0; invocation.setValue ("FAILOVER_COUNTER", new Integer(failoverCounter), PayloadKey.AS_IS); // optimize if calling another bean in same EJB-application if (isLocal(invocation)) { return InvokerInterceptor.getLocal().invoke(invocation); } else { // We are going to go through a Remote invocation, switch to a Marshalled Invocation MarshalledInvocation mi = new MarshalledInvocation(invocation); // Set the transaction propagation context mi.setTransactionPropagationContext(getTransactionPropagationContext()); mi.setValue("CLUSTER_VIEW_ID", new Long(this.familyClusterInfo.getCurrentViewId ())); Invoker target = (Invoker)getRemoteTarget(invocation); boolean failoverAuthorized = true; Exception lastException = null; while (target != null && failoverAuthorized) { boolean definitivlyRemoveNodeOnFailure = true; try { if( trace ) log.trace("Invoking on target="+target); Object rtnObj = target.invoke(mi); HARMIResponse rsp = null; if (rtnObj instanceof MarshalledObject) { rsp = (HARMIResponse)((MarshalledObject)rtnObj).get(); } else { rsp = (HARMIResponse)rtnObj; } if (rsp.newReplicants != null) { if( trace ) { log.trace("newReplicants: "+rsp.newReplicants); } updateClusterInfo (rsp.newReplicants, rsp.currentViewId); } //else System.out.println("Static set of replicants: " + this.familyClusterInfo.getCurrentViewId () + " (me = " + this + ")"); invocationHasReachedAServer (invocation); return rsp.response; } catch (java.net.ConnectException e) { lastException = e; } catch (java.net.UnknownHostException e) { lastException = e; } catch (java.rmi.ConnectException e) { lastException = e; if(e.getCause() != null && e.getCause() instanceof java.io.EOFException) { // don't failover as we may of reached the target invocationHasReachedAServer (invocation); throw e; } } catch (java.rmi.ConnectIOException e) { lastException = e; } catch (java.rmi.NoSuchObjectException e) { lastException = e; } catch (java.rmi.UnknownHostException e) { lastException = e; } catch (GenericClusteringException e) { lastException = e; // this is a generic clustering exception that contain the // completion status: usefull to determine if we are authorized // to re-issue a query to another node // if (e.getCompletionStatus () == GenericClusteringException.COMPLETED_NO) { // we don't want to remove the node from the list of failed // node UNLESS there is a risk to indefinitively loop // if (totalNumberOfTargets() >= failoverCounter) { if (!e.isDefinitive ()) definitivlyRemoveNodeOnFailure = false; } } else { invocationHasReachedAServer (invocation); throw new ServerException("Clustering error", e); } } catch (ServerException e) { //Why do NoSuchObjectExceptions get ignored for a retry here //unlike in the non-HA case? invocationHasReachedAServer (invocation); if (e.detail instanceof TransactionRolledbackException) { throw (TransactionRolledbackException) e.detail; } if (e.detail instanceof RemoteException) { throw (RemoteException) e.detail; } throw e; } catch (Exception e) { lastException = e; invocationHasReachedAServer (invocation); throw e; } if( trace ) log.trace("Invoke failed, target="+target, lastException); // If we reach here, this means that we must fail-over remoteTargetHasFailed(target); if (!definitivlyRemoveNodeOnFailure) { resetView (); } failoverAuthorized = txContextAllowsFailover (invocation); target = (Invoker)getRemoteTarget(invocation); failoverCounter++; mi.setValue ("FAILOVER_COUNTER", new Integer(failoverCounter), PayloadKey.AS_IS); } // if we get here this means list was exhausted String msg = "Service unavailable."; if (failoverAuthorized == false) { msg = "Service unavailable (failover not possible inside a user transaction)."; } throw new ServiceUnavailableException(msg, lastException); } } /** * Externalize this instance. * * If this instance lives in a different VM than its container * invoker, the remote interface of the container invoker is * not externalized. */ public void writeExternal(final ObjectOutput out) throws IOException { // JBAS-2071 - sync on FCI to ensure targets and vid are consistent ArrayList targets = null; long vid = 0; synchronized (this.familyClusterInfo) { // JBAS-6345 -- write an ArrayList for compatibility with AS 3.x/4.x clients targets = new ArrayList(this.familyClusterInfo.getTargets ()); vid = this.familyClusterInfo.getCurrentViewId (); } out.writeObject(targets); out.writeObject(this.loadBalancePolicy); out.writeObject (this.proxyFamilyName); out.writeLong (vid); } /** * Un-externalize this instance. * * We check timestamps of the interfaces to see if the instance is in the original VM of creation */ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { List targets = (List)in.readObject(); this.loadBalancePolicy = (LoadBalancePolicy)in.readObject(); this.proxyFamilyName = (String)in.readObject(); long vid = in.readLong (); // keep a reference on our family object // this.familyClusterInfo = ClusteringTargetsRepository.initTarget (this.proxyFamilyName, targets, vid); this.trace = log.isTraceEnabled(); if( trace ) log.trace("Init, clusterInfo: "+familyClusterInfo+", policy="+loadBalancePolicy); } /** * Overriden method to rethrow any potential SystemException arising from it. * Looking at the parent implementation, none of the methods called actually * throw SystemException. */ public Object getTransactionPropagationContext() { Object tpc; try { tpc = super.getTransactionPropagationContext(); } catch (SystemException e) { throw new RuntimeException("Unable to retrieve transaction propagation context", e); } return tpc; } // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- }