001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 */
019
020package org.apache.hadoop.hbase.ipc;
021
022import static org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType.REGION_NAME;
023
024import edu.umd.cs.findbugs.annotations.Nullable;
025import java.io.IOException;
026import java.io.InterruptedIOException;
027import org.apache.hadoop.hbase.DoNotRetryIOException;
028import org.apache.hadoop.hbase.HConstants;
029import org.apache.hadoop.hbase.exceptions.UnknownProtocolException;
030import org.apache.hadoop.util.StringUtils;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
036import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors;
037import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.MethodDescriptor;
038import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.ServiceDescriptor;
039import org.apache.hbase.thirdparty.com.google.protobuf.Message;
040import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
041import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
042import org.apache.hbase.thirdparty.com.google.protobuf.Service;
043
044import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
045import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceCall;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceRequest;
049import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType;
050
051/**
052 * Utilities for handling coprocessor rpc service calls.
053 */
054@InterfaceAudience.Private
055public final class CoprocessorRpcUtils {
056  private static final Logger LOG = LoggerFactory.getLogger(CoprocessorRpcUtils.class);
057  /**
058   * We assume that all HBase protobuf services share a common package name
059   * (defined in the .proto files).
060   */
061  private static final String hbaseServicePackage;
062  static {
063    Descriptors.ServiceDescriptor clientService = ClientProtos.ClientService.getDescriptor();
064    hbaseServicePackage = clientService.getFullName()
065        .substring(0, clientService.getFullName().lastIndexOf(clientService.getName()));
066  }
067
068  private CoprocessorRpcUtils() {
069    // private for utility class
070  }
071
072  /**
073   * Returns the name to use for coprocessor service calls.  For core HBase services
074   * (in the hbase.pb protobuf package), this returns the unqualified name in order to provide
075   * backward compatibility across the package name change.  For all other services,
076   * the fully-qualified service name is used.
077   */
078  public static String getServiceName(Descriptors.ServiceDescriptor service) {
079    if (service.getFullName().startsWith(hbaseServicePackage)) {
080      return service.getName();
081    }
082    return service.getFullName();
083  }
084
085  public static CoprocessorServiceRequest getCoprocessorServiceRequest(
086      final Descriptors.MethodDescriptor method, final Message request) {
087    return getCoprocessorServiceRequest(method, request, HConstants.EMPTY_BYTE_ARRAY,
088        HConstants.EMPTY_BYTE_ARRAY);
089  }
090
091  public static CoprocessorServiceRequest getCoprocessorServiceRequest(
092    final Descriptors.MethodDescriptor method, final Message request, final byte[] row,
093    final byte[] regionName) {
094    return CoprocessorServiceRequest.newBuilder()
095      .setCall(getCoprocessorServiceCall(method, request, row))
096      .setRegion(RequestConverter.buildRegionSpecifier(REGION_NAME, regionName)).build();
097  }
098
099  private static CoprocessorServiceCall getCoprocessorServiceCall(final MethodDescriptor method,
100    final Message request, final byte[] row) {
101    return CoprocessorServiceCall.newBuilder()
102      .setRow(org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations.unsafeWrap(row))
103      .setServiceName(CoprocessorRpcUtils.getServiceName(method.getService()))
104      .setMethodName(method.getName())
105      // TODO!!!!! Come back here after!!!!! This is a double copy of the request if I read
106      // it right copying from non-shaded to shaded version!!!!!! FIXXXXX!!!!!
107      .setRequest(org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations
108        .unsafeWrap(request.toByteArray()))
109      .build();
110  }
111
112  public static MethodDescriptor getMethodDescriptor(final String methodName,
113    final ServiceDescriptor serviceDesc) throws UnknownProtocolException {
114    MethodDescriptor methodDesc = serviceDesc.findMethodByName(methodName);
115    if (methodDesc == null) {
116      throw new UnknownProtocolException(
117        "Unknown method " + methodName + " called on service " + serviceDesc.getFullName());
118    }
119    return methodDesc;
120  }
121
122  public static Message getRequest(Service service, MethodDescriptor methodDesc,
123    ByteString shadedRequest) throws IOException {
124    Message.Builder builderForType = service.getRequestPrototype(methodDesc).newBuilderForType();
125    ProtobufUtil.mergeFrom(builderForType,
126      // TODO: COPY FROM SHADED TO NON_SHADED. DO I HAVE TOO?
127      shadedRequest.toByteArray());
128    return builderForType.build();
129  }
130
131  public static Message getResponse(ClientProtos.CoprocessorServiceResponse result,
132    Message responsePrototype) throws IOException {
133    Message response;
134    if (result.getValue().hasValue()) {
135      Message.Builder builder = responsePrototype.newBuilderForType();
136      builder.mergeFrom(result.getValue().getValue().newInput());
137      response = builder.build();
138    } else {
139      response = responsePrototype.getDefaultInstanceForType();
140    }
141    if (LOG.isTraceEnabled()) {
142      LOG.trace("Master Result is value=" + response);
143    }
144    return response;
145  }
146
147  public static ClientProtos.CoprocessorServiceResponse getResponse(final Message result,
148    final byte[] regionName) {
149    ClientProtos.CoprocessorServiceResponse.Builder builder =
150      ClientProtos.CoprocessorServiceResponse.newBuilder();
151    builder.setRegion(
152      RequestConverter.buildRegionSpecifier(RegionSpecifierType.REGION_NAME, regionName));
153    // TODO: UGLY COPY IN HERE!!!!
154    builder.setValue(builder.getValueBuilder().setName(result.getClass().getName()).setValue(
155      org.apache.hbase.thirdparty.com.google.protobuf.ByteString.copyFrom(result.toByteArray())));
156    return builder.build();
157  }
158
159  /**
160   * Simple {@link RpcCallback} implementation providing a
161   * {@link java.util.concurrent.Future}-like {@link BlockingRpcCallback#get()} method, which
162   * will block util the instance's {@link BlockingRpcCallback#run(Object)} method has been called.
163   * {@code R} is the RPC response type that will be passed to the {@link #run(Object)} method.
164   */
165  @InterfaceAudience.Private
166  // Copy of BlockingRpcCallback but deriving from RpcCallback non-shaded.
167  public static class BlockingRpcCallback<R> implements RpcCallback<R> {
168    private R result;
169    private boolean resultSet = false;
170
171    /**
172     * Called on completion of the RPC call with the response object, or {@code null} in the case of
173     * an error.
174     * @param parameter the response object or {@code null} if an error occurred
175     */
176    @Override
177    public void run(R parameter) {
178      synchronized (this) {
179        result = parameter;
180        resultSet = true;
181        this.notifyAll();
182      }
183    }
184
185    /**
186     * Returns the parameter passed to {@link #run(Object)} or {@code null} if a null value was
187     * passed.  When used asynchronously, this method will block until the {@link #run(Object)}
188     * method has been called.
189     * @return the response object or {@code null} if no response was passed
190     */
191    public synchronized R get() throws IOException {
192      while (!resultSet) {
193        try {
194          this.wait();
195        } catch (InterruptedException ie) {
196          InterruptedIOException exception = new InterruptedIOException(ie.getMessage());
197          exception.initCause(ie);
198          throw exception;
199        }
200      }
201      return result;
202    }
203  }
204
205  /**
206   * Stores an exception encountered during RPC invocation so it can be passed back
207   * through to the client.
208   * @param controller the controller instance provided by the client when calling the service
209   * @param ioe the exception encountered
210   */
211  public static void setControllerException(RpcController controller, IOException ioe) {
212    if (controller == null) {
213      return;
214    }
215    if (controller instanceof org.apache.hadoop.hbase.ipc.ServerRpcController) {
216      ((ServerRpcController)controller).setFailedOn(ioe);
217    } else {
218      controller.setFailed(StringUtils.stringifyException(ioe));
219    }
220  }
221
222  /**
223   * Retreivies exception stored during RPC invocation.
224   * @param controller the controller instance provided by the client when calling the service
225   * @return exception if any, or null; Will return DoNotRetryIOException for string represented
226   * failure causes in controller.
227   */
228  @Nullable
229  public static IOException getControllerException(RpcController controller) throws IOException {
230    if (controller == null || !controller.failed()) {
231      return null;
232    }
233    if (controller instanceof ServerRpcController) {
234      return ((ServerRpcController)controller).getFailedOn();
235    }
236    return new DoNotRetryIOException(controller.errorText());
237  }
238}