/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.dubbo.rpc.cluster.support.wrapper; | |
import org.apache.dubbo.common.URL; | |
import org.apache.dubbo.common.logger.Logger; | |
import org.apache.dubbo.common.logger.LoggerFactory; | |
import org.apache.dubbo.common.utils.CollectionUtils; | |
import org.apache.dubbo.common.utils.StringUtils; | |
import org.apache.dubbo.rpc.AsyncRpcResult; | |
import org.apache.dubbo.rpc.Invocation; | |
import org.apache.dubbo.rpc.Invoker; | |
import org.apache.dubbo.rpc.Result; | |
import org.apache.dubbo.rpc.RpcException; | |
import org.apache.dubbo.rpc.RpcInvocation; | |
import org.apache.dubbo.rpc.cluster.ClusterInvoker; | |
import org.apache.dubbo.rpc.cluster.Directory; | |
import org.apache.dubbo.rpc.support.MockInvoker; | |
import java.util.List; | |
import static org.apache.dubbo.rpc.Constants.MOCK_KEY; | |
import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK; | |
public class MockClusterInvoker<T> implements ClusterInvoker<T> { | |
private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class); | |
private final Directory<T> directory; | |
private final Invoker<T> invoker; | |
public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) { | |
this.directory = directory; | |
this.invoker = invoker; | |
} | |
@Override | |
public URL getUrl() { | |
return directory.getConsumerUrl(); | |
} | |
public URL getRegistryUrl() { | |
return directory.getUrl(); | |
} | |
@Override | |
public Directory<T> getDirectory() { | |
return directory; | |
} | |
@Override | |
public boolean isDestroyed() { | |
return directory.isDestroyed(); | |
} | |
@Override | |
public boolean isAvailable() { | |
return directory.isAvailable(); | |
} | |
@Override | |
public void destroy() { | |
this.invoker.destroy(); | |
} | |
@Override | |
public Class<T> getInterface() { | |
return directory.getInterface(); | |
} | |
@Override | |
public Result invoke(Invocation invocation) throws RpcException { | |
Result result = null; | |
String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim(); | |
if (value.length() == 0 || "false".equalsIgnoreCase(value)) { | |
//no mock | |
result = this.invoker.invoke(invocation); | |
} else if (value.startsWith("force")) { | |
if (logger.isWarnEnabled()) { | |
logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl()); | |
} | |
//force:direct mock | |
result = doMockInvoke(invocation, null); | |
} else { | |
//fail-mock | |
try { | |
result = this.invoker.invoke(invocation); | |
//fix:#4585 | |
if(result.getException() != null && result.getException() instanceof RpcException){ | |
RpcException rpcException= (RpcException)result.getException(); | |
if(rpcException.isBiz()){ | |
throw rpcException; | |
}else { | |
result = doMockInvoke(invocation, rpcException); | |
} | |
} | |
} catch (RpcException e) { | |
if (e.isBiz()) { | |
throw e; | |
} | |
if (logger.isWarnEnabled()) { | |
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e); | |
} | |
result = doMockInvoke(invocation, e); | |
} | |
} | |
return result; | |
} | |
@SuppressWarnings({"unchecked", "rawtypes"}) | |
private Result doMockInvoke(Invocation invocation, RpcException e) { | |
Result result = null; | |
Invoker<T> minvoker; | |
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation); | |
if (CollectionUtils.isEmpty(mockInvokers)) { | |
minvoker = (Invoker<T>) new MockInvoker(getUrl(), directory.getInterface()); | |
} else { | |
minvoker = mockInvokers.get(0); | |
} | |
try { | |
result = minvoker.invoke(invocation); | |
} catch (RpcException me) { | |
if (me.isBiz()) { | |
result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation); | |
} else { | |
throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause()); | |
} | |
} catch (Throwable me) { | |
throw new RpcException(getMockExceptionMessage(e, me), me.getCause()); | |
} | |
return result; | |
} | |
private String getMockExceptionMessage(Throwable t, Throwable mt) { | |
String msg = "mock error : " + mt.getMessage(); | |
if (t != null) { | |
msg = msg + ", invoke error is :" + StringUtils.toString(t); | |
} | |
return msg; | |
} | |
/** | |
* Return MockInvoker | |
* Contract: | |
* directory.list() will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return. | |
* if directory.list() returns more than one mock invoker, only one of them will be used. | |
* | |
* @param invocation | |
* @return | |
*/ | |
private List<Invoker<T>> selectMockInvoker(Invocation invocation) { | |
List<Invoker<T>> invokers = null; | |
//TODO generic invoker? | |
if (invocation instanceof RpcInvocation) { | |
//Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachment needs to be improved) | |
((RpcInvocation) invocation).setAttachment(INVOCATION_NEED_MOCK, Boolean.TRUE.toString()); | |
//directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return. | |
try { | |
invokers = directory.list(invocation); | |
} catch (RpcException e) { | |
if (logger.isInfoEnabled()) { | |
logger.info("Exception when try to invoke mock. Get mock invokers error for service:" | |
+ getUrl().getServiceInterface() + ", method:" + invocation.getMethodName() | |
+ ", will construct a new mock with 'new MockInvoker()'.", e); | |
} | |
} | |
} | |
return invokers; | |
} | |
@Override | |
public String toString() { | |
return "invoker :" + this.invoker + ",directory: " + this.directory; | |
} | |
} |