blob: 77144ae7d492d0287842b2cd642d23ed0f1464e1 [file] [log] [blame]
/*
* 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.drill.exec.util;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
public class ProtobufPatcher {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ProtobufPatcher.class);
private static volatile boolean patchingAttempted = false;
/**
* Makes protobuf version 3.6+ compatible to libraries that still use protobuf 2.5.0.
*/
public static synchronized void patch() {
if (!patchingAttempted) {
try {
patchingAttempted = true;
patchByteString();
patchGeneratedMessageLite();
patchGeneratedMessageLiteBuilder();
} catch (Exception e) {
logger.warn("Unable to patch Protobuf.", e);
}
}
}
/**
* HBase client overrides methods from {@link com.google.protobuf.ByteString},
* that were made final in version 3.6+ of protobuf.
* This method removes the final modifiers. It also creates and loads classes
* that were made private nested in protobuf 3.6+ to be accessible by the old fully qualified name.
*
* @throws NotFoundException if unable to find a method or class to patch.
* @throws CannotCompileException if unable to compile the patched class.
*/
private static void patchByteString() throws NotFoundException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass byteString = classPool.get("com.google.protobuf.ByteString");
removeFinal(byteString.getDeclaredMethod("toString"));
removeFinal(byteString.getDeclaredMethod("hashCode"));
removeFinal(byteString.getDeclaredMethod("iterator"));
// Need to inherit from these classes to make them accessible by the old path.
CtClass googleLiteralByteString = classPool.get("com.google.protobuf.ByteString$LiteralByteString");
removePrivate(googleLiteralByteString);
CtClass googleBoundedByteString = classPool.get("com.google.protobuf.ByteString$BoundedByteString");
removePrivate(googleBoundedByteString);
removeFinal(googleBoundedByteString);
for (CtMethod ctMethod : googleLiteralByteString.getDeclaredMethods()) {
removeFinal(ctMethod);
}
byteString.toClass();
googleLiteralByteString.toClass();
googleBoundedByteString.toClass();
// Adding the classes back to the old path.
CtClass literalByteString = classPool.makeClass("com.google.protobuf.LiteralByteString");
literalByteString.setSuperclass(googleLiteralByteString);
literalByteString.toClass();
CtClass boundedByteString = classPool.makeClass("com.google.protobuf.BoundedByteString");
boundedByteString.setSuperclass(googleBoundedByteString);
boundedByteString.toClass();
}
/**
* MapR-DB client extends {@link com.google.protobuf.GeneratedMessageLite} and overrides some methods,
* that were made final in version 3.6+ of protobuf.
* This method removes the final modifiers.
*
* @throws NotFoundException if unable to find a method or class to patch.
* @throws CannotCompileException if unable to compile the patched method body.
*/
private static void patchGeneratedMessageLite() throws NotFoundException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass generatedMessageLite = classPool.get("com.google.protobuf.GeneratedMessageLite");
removeFinal(generatedMessageLite.getDeclaredMethod("getParserForType"));
removeFinal(generatedMessageLite.getDeclaredMethod("isInitialized"));
// The method was removed, but it is used in com.mapr.fs.proto.Dbserver.
// Adding it back.
generatedMessageLite.addMethod(CtNewMethod.make("protected void makeExtensionsImmutable() { }", generatedMessageLite));
// A constructor with this signature was removed. Adding it back.
generatedMessageLite.addConstructor(CtNewConstructor.make("protected GeneratedMessageLite(com.google.protobuf.GeneratedMessageLite.Builder builder) { }", generatedMessageLite));
// This single method was added instead of several abstract methods.
// MapR-DB client doesn't use it, but it was added in overridden equals() method.
// Adding default implementation.
CtMethod dynamicMethod = generatedMessageLite.getDeclaredMethod("dynamicMethod", new CtClass[] {
classPool.get("com.google.protobuf.GeneratedMessageLite$MethodToInvoke"),
classPool.get("java.lang.Object"),
classPool.get("java.lang.Object")});
addImplementation(dynamicMethod, "if ($1.equals(com.google.protobuf.GeneratedMessageLite.MethodToInvoke.GET_DEFAULT_INSTANCE)) {" +
" return this;" +
"} else {" +
" return null;" +
"}");
generatedMessageLite.toClass();
}
/**
* MapR-DB client extends {@link com.google.protobuf.GeneratedMessageLite.Builder} and overrides some methods,
* that were made final in version 3.6+ of protobuf.
* This method removes the final modifiers.
* Also, adding back a default constructor that was removed.
*
* @throws NotFoundException if unable to find a method or class to patch.
* @throws CannotCompileException if unable to add a default constructor.
*/
private static void patchGeneratedMessageLiteBuilder() throws NotFoundException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass builder = classPool.get("com.google.protobuf.GeneratedMessageLite$Builder");
removeFinal(builder.getDeclaredMethod("isInitialized"));
removeFinal(builder.getDeclaredMethod("clear"));
builder.addConstructor(CtNewConstructor.defaultConstructor(builder));
builder.toClass();
}
/**
* Removes final modifier from a given method.
*
* @param ctMethod method which need to be non-final.
*/
private static void removeFinal(CtMethod ctMethod) {
int modifiers = Modifier.clear(ctMethod.getModifiers(), Modifier.FINAL);
ctMethod.setModifiers(modifiers);
}
/**
* Removes final modifier from a given class.
*
* @param ctClass method which need to be non-final.
*/
private static void removeFinal(CtClass ctClass) {
int modifiers = Modifier.clear(ctClass.getModifiers(), Modifier.FINAL);
ctClass.setModifiers(modifiers);
}
/**
* Removes private modifier from a given class
*
* @param ctClass class which need to be non-private.
*/
private static void removePrivate(CtClass ctClass) {
int modifiers = Modifier.clear(ctClass.getModifiers(), Modifier.PRIVATE);
ctClass.setModifiers(modifiers);
}
/**
* Removes abstract modifier and adds implementation to a given method.
*
* @param ctMethod method to process.
* @param methodBody method implementation.
* @throws CannotCompileException if unable to compile given method body.
*/
private static void addImplementation(CtMethod ctMethod, String methodBody) throws CannotCompileException {
ctMethod.setBody(methodBody);
int modifiers = Modifier.clear(ctMethod.getModifiers(), Modifier.ABSTRACT);
ctMethod.setModifiers(modifiers);
}
}