001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.invocation.http.interfaces;
023:
024: import java.io.Externalizable;
025: import java.io.IOException;
026: import java.io.ObjectInput;
027: import java.io.ObjectOutput;
028: import java.lang.reflect.InvocationTargetException;
029: import java.net.URL;
030: import java.rmi.ServerException;
031: import java.util.ArrayList;
032:
033: import org.jboss.ha.framework.interfaces.ClusteringTargetsRepository;
034: import org.jboss.ha.framework.interfaces.GenericClusteringException;
035: import org.jboss.ha.framework.interfaces.HARMIResponse;
036: import org.jboss.ha.framework.interfaces.LoadBalancePolicy;
037: import org.jboss.ha.framework.interfaces.FamilyClusterInfo;
038: import org.jboss.invocation.Invocation;
039: import org.jboss.invocation.InvocationException;
040: import org.jboss.invocation.Invoker;
041: import org.jboss.invocation.InvokerProxyHA;
042: import org.jboss.invocation.MarshalledInvocation;
043: import org.jboss.invocation.PayloadKey;
044: import org.jboss.logging.Logger;
045:
046: /** The client side Http invoker proxy that posts an invocation to the
047: InvokerServlet using the HttpURLConnection created from a target url.
048: This proxy handles failover using its associated LoadBalancePolicy and
049: current list of URL strings. The candidate URLs are updated dynamically
050: after an invocation if the cluster partitation view has changed.
051:
052: * @author Scott.Stark@jboss.org
053: * @version $Revision: 57188 $
054: */
055: public class HttpInvokerProxyHA implements InvokerProxyHA, Invoker,
056: Externalizable {
057: // Constants -----------------------------------------------------
058: private static Logger log = Logger
059: .getLogger(HttpInvokerProxyHA.class);
060:
061: /** Serial Version Identifier.
062: * @since 1.1.4.5
063: */
064: private static final long serialVersionUID = -7081220026780794383L;
065:
066: // Attributes ----------------------------------------------------
067:
068: // URL to the remote JMX node invoker
069: protected LoadBalancePolicy loadBalancePolicy;
070: protected String proxyFamilyName = null;
071: protected FamilyClusterInfo familyClusterInfo = null;
072: /** Trace level logging flag only set when the proxy is created or read from JNDI */
073: protected transient boolean trace = false;
074:
075: // Constructors --------------------------------------------------
076: public HttpInvokerProxyHA() {
077: // For externalization to work
078: }
079:
080: /**
081: * @param targets the list of URLs through which clients should contact the
082: * InvokerServlet.
083: * @param policy the policy for choosing among targets ClusteringTargetsRepository under
084: * which this proxy is to be stored
085: * @param proxyFamilyName the name into the
086: */
087: public HttpInvokerProxyHA(ArrayList targets, long viewId,
088: LoadBalancePolicy policy, String proxyFamilyName) {
089: this .familyClusterInfo = ClusteringTargetsRepository
090: .initTarget(proxyFamilyName, targets, viewId);
091: this .loadBalancePolicy = policy;
092: this .proxyFamilyName = proxyFamilyName;
093: this .trace = log.isTraceEnabled();
094: if (trace)
095: log.trace("Init, cluterInfo: " + familyClusterInfo
096: + ", policy=" + loadBalancePolicy);
097: }
098:
099: // Public --------------------------------------------------------
100:
101: public void updateClusterInfo(ArrayList targets, long viewId) {
102: if (familyClusterInfo != null)
103: this .familyClusterInfo.updateClusterInfo(targets, viewId);
104: }
105:
106: public String getServerHostName() throws Exception {
107: return null;
108: }
109:
110: public FamilyClusterInfo getClusterInfo() {
111: return familyClusterInfo;
112: }
113:
114: public Object getRemoteTarget() {
115: return getRemoteTarget(null);
116: }
117:
118: public Object getRemoteTarget(Invocation invocationBasedRouting) {
119: Object target = loadBalancePolicy.chooseTarget(
120: this .familyClusterInfo, invocationBasedRouting);
121: if (trace)
122: log.trace("Choose remoteTarget: " + target);
123: return target;
124: }
125:
126: public void remoteTargetHasFailed(Object target) {
127: removeDeadTarget(target);
128: }
129:
130: protected int totalNumberOfTargets() {
131: int size = 0;
132: if (familyClusterInfo != null)
133: size = familyClusterInfo.getTargets().size();
134: return size;
135: }
136:
137: protected void removeDeadTarget(Object target) {
138: if (familyClusterInfo != null) {
139: ArrayList targets = familyClusterInfo
140: .removeDeadTarget(target);
141: if (trace) {
142: log.trace("removeDeadTarget(" + target
143: + "), targets.size=" + targets.size());
144: }
145: }
146: }
147:
148: protected void resetView() {
149: familyClusterInfo.resetView();
150: }
151:
152: /** This method builds a MarshalledInvocation from the invocation passed
153: in and then does a post to the target URL.
154: */
155: public Object invoke(Invocation invocation) throws Exception {
156: // we give the opportunity, to any server interceptor, to know if this a
157: // first invocation to a node or if it is a failovered call
158: //
159: int failoverCounter = 0;
160:
161: // We are going to go through a Remote invocation, switch to a Marshalled Invocation
162: MarshalledInvocation mi = new MarshalledInvocation(invocation);
163: mi.setValue("CLUSTER_VIEW_ID", new Long(familyClusterInfo
164: .getCurrentViewId()));
165: String target = (String) getRemoteTarget(invocation);
166: URL externalURL = Util.resolveURL(target);
167: Exception lastException = null;
168: while (externalURL != null) {
169: boolean definitivlyRemoveNodeOnFailure = true;
170: invocation.setValue("FAILOVER_COUNTER", new Integer(
171: failoverCounter), PayloadKey.AS_IS);
172: try {
173: if (trace)
174: log.trace("Invoking on target=" + externalURL);
175: Object rtn = Util.invoke(externalURL, mi);
176: HARMIResponse rsp = (HARMIResponse) rtn;
177:
178: if (rsp.newReplicants != null)
179: updateClusterInfo(rsp.newReplicants,
180: rsp.currentViewId);
181: return rsp.response;
182: } catch (GenericClusteringException e) {
183: // this is a generic clustering exception that contain the
184: // completion status: usefull to determine if we are authorized
185: // to re-issue a query to another node
186: //
187: if (e.getCompletionStatus() != e.COMPLETED_NO) {
188: // we don't want to remove the node from the list of targets
189: // UNLESS there is a risk to loop
190: if (totalNumberOfTargets() >= failoverCounter) {
191: if (e.isDefinitive() == false)
192: definitivlyRemoveNodeOnFailure = false;
193: }
194: } else {
195: throw new ServerException(
196: "Cannot proceed beyond target="
197: + externalURL, e);
198: }
199: } catch (InvocationException e) {
200: // Handle application declared exceptions
201: Throwable cause = e.getTargetException();
202: if (cause instanceof Exception)
203: throw (Exception) cause;
204: else if (cause instanceof Error)
205: throw (Error) cause;
206: throw new InvocationTargetException(cause);
207: } catch (IOException e) {
208: if (trace)
209: log
210: .trace("Invoke failed, target="
211: + externalURL, e);
212: lastException = e;
213: } catch (Exception e) {
214: // Rethrow for the application to handle
215: throw e;
216: }
217:
218: // If we reach here, this means that we must fail-over
219: remoteTargetHasFailed(target);
220: if (definitivlyRemoveNodeOnFailure)
221: resetView();
222: target = (String) getRemoteTarget(invocation);
223: externalURL = Util.resolveURL(target);
224: failoverCounter++;
225: }
226: // if we get here this means list was exhausted
227: throw new ServerException(
228: "Service unavailable last exception:", lastException);
229: }
230:
231: /** Externalize this instance.
232: */
233: public void writeExternal(final ObjectOutput out)
234: throws IOException {
235: // JBAS-2071 - sync on FCI to ensure targets and vid are consistent
236: ArrayList currentTargets = null;
237: long vid = 0;
238: synchronized (this .familyClusterInfo) {
239: currentTargets = this .familyClusterInfo.getTargets();
240: vid = this .familyClusterInfo.getCurrentViewId();
241: }
242: out.writeObject(currentTargets);
243: out.writeLong(vid);
244: out.writeObject(this .loadBalancePolicy);
245: out.writeObject(this .proxyFamilyName);
246: }
247:
248: /** Un-externalize this instance.
249: */
250: public void readExternal(final ObjectInput in) throws IOException,
251: ClassNotFoundException {
252: ArrayList targets = (ArrayList) in.readObject();
253: long vid = in.readLong();
254: this .loadBalancePolicy = (LoadBalancePolicy) in.readObject();
255: this .proxyFamilyName = (String) in.readObject();
256: this .trace = log.isTraceEnabled();
257:
258: // keep a reference on our family object
259: this .familyClusterInfo = ClusteringTargetsRepository
260: .initTarget(this .proxyFamilyName, targets, vid);
261: if (trace)
262: log.trace("Init, clusterInfo: " + familyClusterInfo
263: + ", policy=" + loadBalancePolicy);
264: }
265: }
|