/* * 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.ha.framework.interfaces; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.rmi.ConnectException; import java.rmi.ConnectIOException; import java.rmi.NoSuchObjectException; import java.rmi.RemoteException; import java.rmi.UnknownHostException; import java.util.ArrayList; import java.util.List; import org.jboss.ha.client.loadbalance.LoadBalancePolicy; import org.jboss.invocation.MarshalledInvocation; import org.jboss.logging.Logger; /** * * * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 83230 $ */ public class HARMIClient implements HARMIProxy, java.lang.reflect.InvocationHandler, java.io.Serializable { // Constants ----------------------------------------------------- /** The serialVersionUID * @since */ private static final long serialVersionUID = -1227816478666532463L; private static final Logger log = Logger.getLogger(HARMIClient.class); /** {@link Object#toString} method reference. */ protected static final Method TO_STRING; /** {@link Object#hashCode} method reference. */ protected static final Method HASH_CODE; /** {@link Object#equals} method reference. */ protected static final Method EQUALS; static { try { final Class[] empty = {}; final Class type = Object.class; TO_STRING = type.getMethod("toString", empty); HASH_CODE = type.getMethod("hashCode", empty); EQUALS = type.getMethod("equals", new Class[] { type }); } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError(e); } } // Attributes ---------------------------------------------------- protected String key = null; //protected ArrayList targets = null; protected LoadBalancePolicy loadBalancePolicy; //protected transient long currentViewId = 0; protected transient Object local = null; protected transient boolean trace; FamilyClusterInfo familyClusterInfo = null; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public HARMIClient() {} public HARMIClient(List targets, LoadBalancePolicy policy, String key) { this(targets, 0, policy, key, null); } public HARMIClient(List targets, long initViewId, LoadBalancePolicy policy, String key, Object local) { this.familyClusterInfo = ClusteringTargetsRepository.initTarget (key, targets, initViewId); //this.targets = targets; this.loadBalancePolicy = policy; if (this.loadBalancePolicy instanceof org.jboss.ha.framework.interfaces.LoadBalancePolicy) { ((org.jboss.ha.framework.interfaces.LoadBalancePolicy)this.loadBalancePolicy).init(this); } this.key = key; this.local = local; this.trace = log.isTraceEnabled(); if( trace ) log.trace("Init, cluterInfo: "+familyClusterInfo+", policy="+loadBalancePolicy); } // Public -------------------------------------------------------- /* public ArrayList getTargets() { return targets; } public void setTargets(ArrayList newTargets) { synchronized(targets) { targets.clear(); targets.addAll(newTargets); } } */ public void updateClusterInfo (ArrayList targets, long viewId) { if (familyClusterInfo != null) this.familyClusterInfo.updateClusterInfo (targets, viewId); } public Object getRemoteTarget() { // System.out.println("number of targets: " + targets.size()); return loadBalancePolicy.chooseTarget(this.familyClusterInfo); // legacy, no Invocation object in raw HA-RMI } public void remoteTargetHasFailed(Object target) { removeDeadTarget(target); } public Method findLocalMethod(Method method, Object[] args) throws Exception { return method; } /** * Invoke the given method against a remote server. If the call results * in certain {@link RemoteException} subtypes, catch the exception and * attempt to fail over to another server. * <p> * Failover will only be attempted if the remote call throws an exception * whose type indicates the call never reached the server: * <ul> * <li>{@link java.rmi.ConnectException}</li> * <li>{@link java.rmi.ConnectIOException}</li> * <li>{@link java.rmi.NoSuchObjectException}</li> * <li>{@link java.rmi.UnknownHostException}</li> * </ul> * </p> * <p> * All other exception types will not be caught. * </p> * <p> * If one of the above exception types is caught when invoking against the * last known server, then a {@link RemoteException} will be thrown. This * exception will include as its {@link Throwable#getCause() cause} either * <ol> * <li>any {@link java.rmi.NoSuchObjectException} that was caught</li> * <li>or, if no {@link java.rmi.NoSuchObjectException} that was caught, * the exception thrown on the last failover attempt</li> * </ol> * Preference is given to including <code>NoSuchObjectException</code> as * the cause, as that exception indicates that a server was listening on * the expected address and port but that this client has an RMI stub that * is out of sync with the server. This would typically happen due to * a server restart or service redeploy. Knowledge of this failure condition * could potentially be useful to the caller. * </p> * * @param proxy the proxy object that's being invoked * @param method the method to invoke * @param args arguments to the method * @return any return value from the invocation, or <code>null</code> * * @throws Throwable Throwable thrown when making remote call, or the * <code>RemoteException</code> discussed above. */ public Object invokeRemote(Object proxy, Method method, Object[] args) throws Throwable { boolean trace = log.isTraceEnabled(); HARMIServer target = (HARMIServer)getRemoteTarget(); NoSuchObjectException nsoe = null; Exception lastException = null; while (target != null) { try { if( trace ) log.trace("Invoking on target="+target); MarshalledInvocation mi = new MarshalledInvocation(null, method, args, null, null, null); mi.setObjectName (""); //FIXME: Fake value! Bill's optimisations regarding MI make the hypothesis // that ObjectName is always here otherwise the writeExternal code of MI // "out.writeInt(payload.size() - 3);" is wrong HARMIResponse rsp = target.invoke(this.familyClusterInfo.getCurrentViewId (), mi); if (rsp.newReplicants != null) { if( trace ) { log.trace("newReplicants: "+rsp.newReplicants); } updateClusterInfo (rsp.newReplicants, rsp.currentViewId); //setTargets(rsp.newReplicants); //currentViewId = rsp.currentViewId; } return rsp.response; } catch (ConnectException e) { lastException = e; } catch (ConnectIOException e) { lastException = e; } catch (NoSuchObjectException e) { // JBAS-4740 preserve this exception nsoe = e; lastException = e; } catch (UnknownHostException e) { lastException = e; } if( trace ) log.trace("Invoke failed, target="+target, lastException); // If we reach here, this means that we must fail-over remoteTargetHasFailed(target); target = (HARMIServer)getRemoteTarget(); } // if we get here this means list was exhausted // JBAS-4740 wrap any NSOE in preference to 'lastException' since // an NSOE indicates a server was running Exception toWrap = (nsoe == null) ? lastException : nsoe; throw new java.rmi.RemoteException("Service unavailable.", toWrap); } // HARMIProxy implementation ---------------------------------------------- public boolean isLocal() { return local != null; } // InvocationHandler implementation ---------------------------------------------- /** * Invoke the given method, locally if possible; if not then * {@link #invokeRemote(Object, Method, Object[]) invoke against a remote server}. * * @see #invokeRemote(Object, Method, Object[]) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // The isLocal call is handled by the proxy String name = method.getName(); if (method.equals(TO_STRING)) { StringBuffer tmp = new StringBuffer(super.toString()); tmp.append('('); tmp.append(familyClusterInfo); tmp.append(')'); return tmp.toString(); } else if (name.equals("equals")) { return method.invoke(this, args); } else if (name.equals("hashCode")) { return method.invoke(this, args); } else if (name.equals("isLocal") && (args == null || args.length == 0)) { return method.invoke(this, args); } // we try to optimize the call locally first // if (local != null) { try { Method localMethod = findLocalMethod(method, args); return localMethod.invoke(local, args); } catch (java.lang.reflect.InvocationTargetException ite) { throw ite.getTargetException(); } } else { return invokeRemote(null, method, args); } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- protected void removeDeadTarget(Object target) { //System.out.println("Size before : " + Integer.toString(targets.length)); if (this.familyClusterInfo != null) this.familyClusterInfo.removeDeadTarget (target); } // Private ------------------------------------------------------- private void readObject (ObjectInputStream stream) throws IOException, ClassNotFoundException { this.key = stream.readUTF(); List targets = (List)stream.readObject(); long vid = stream.readLong (); this.loadBalancePolicy = (LoadBalancePolicy)stream.readObject(); HARMIServer server = (HARMIServer)HARMIServer.rmiServers.get(key); // keep a reference on our family object // this.familyClusterInfo = ClusteringTargetsRepository.initTarget (this.key, targets, vid); if (this.loadBalancePolicy instanceof org.jboss.ha.framework.interfaces.LoadBalancePolicy) { ((org.jboss.ha.framework.interfaces.LoadBalancePolicy)this.loadBalancePolicy).init(this); } if (server != null) { synchronized (targets) { try { targets = (List)server.getReplicants(); local = server.getLocal(); } catch (Exception ignored) {} } } this.trace = log.isTraceEnabled(); if( trace ) log.trace("Init, clusterInfo: "+familyClusterInfo+", policy="+loadBalancePolicy); } @SuppressWarnings("unchecked") private void writeObject (ObjectOutputStream stream) throws IOException { // JBAS-2071 - sync on FCI to ensure targets and vid are consistent ArrayList currentTargets = null; long vid = 0; synchronized (this.familyClusterInfo) { // JBAS-6345 -- write an ArrayList for compatibility with AS 3.x/4.x clients currentTargets = new ArrayList(this.familyClusterInfo.getTargets()); vid = this.familyClusterInfo.getCurrentViewId (); } stream.writeUTF(key); stream.writeObject(currentTargets); stream.writeLong(vid); stream.writeObject(loadBalancePolicy); } }