// Path: autoconfigure/src/main/java/ru/trylogic/spring/boot/thrift/aop/LoggingThriftMethodInterceptor.java
// @Slf4j
// public class LoggingThriftMethodInterceptor implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
// 
//     @Override
//     public void before(Method method, Object[] args, Object target) throws Throwable {
//         log.info("Thrift method {}.{}() is called with args: {}",
//                  target.getClass().getSimpleName(),
//                  method.getName(),
//                  args);
//     }
// 
//     @Override
//     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
//         log.info("Thrift method {}.{}() returns this: {}",
//                  target.getClass().getSimpleName(),
//                  method.getName(),
//                  returnValue);
//     }
// 
//     @SuppressWarnings("unused")
//     public void afterThrowing(Method method, Object[] args, Object target, Exception e) throws Throwable {
//         if (!(e instanceof TException)) {
//             log.warn("Unexpected exception in " + target.getClass().getCanonicalName() + "." + method.getName(), e);
//             throw new TApplicationException(TApplicationException.INTERNAL_ERROR, e.toString());
//         }
//         log.warn("Exception in Thrift method {}.{}() when called with args: {}", target.getClass().getSimpleName(), method.getName(), args, e);
//     }
// }
// 
// Path: autoconfigure/src/main/java/ru/trylogic/spring/boot/thrift/aop/MetricsThriftMethodInterceptor.java
// @RequiredArgsConstructor
// public class MetricsThriftMethodInterceptor implements MethodInterceptor {
//     private static final String THRIFT_REQUEST_DURATION_METRIC = "thrift.request.duration";
// 
//     private final MeterRegistry meterRegistry;
// 
//     @Override
//     public Object invoke(MethodInvocation invocation) throws Throwable {
//         final long startTime = System.nanoTime();
// 
//         try {
//             final Object result = invocation.proceed();
//             time(invocation, startTime);
//             return result;
//         } catch (Exception e) {
//             time(invocation, startTime, e);
//             throw e;
//         } finally {
//             meterRegistry.gauge("timer.thrift." + invocation.getThis().getClass().getCanonicalName() + "." + invocation.getMethod().getName(), System.currentTimeMillis() - startTime);
//         }
//     }
// 
//     private void time(MethodInvocation invocation, long startTime) {
//         time(invocation, startTime, null);
//     }
// 
//     private void time(MethodInvocation invocation, long startTime, Exception exception) {
//         Timer
//                 .builder(THRIFT_REQUEST_DURATION_METRIC)
//                 .publishPercentileHistogram()
//                 .description("Thrift handler request duration")
//                 .tags(
//                         "handler", invocation.getThis().getClass().getCanonicalName(),
//                         "method", invocation.getMethod().getName(),
//                         "status", exception == null ? "ok" : exception.getClass().getCanonicalName()
//                 )
//                 .register(meterRegistry)
//                 .record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
//     }
// }


import io.micrometer.core.instrument.MeterRegistry;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.RegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import ru.trylogic.spring.boot.thrift.annotation.ThriftController;
import ru.trylogic.spring.boot.thrift.aop.LoggingThriftMethodInterceptor;
import ru.trylogic.spring.boot.thrift.aop.MetricsThriftMethodInterceptor;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import java.lang.reflect.Constructor;
        @Autowired
        private TProtocolFactory protocolFactory;

        @Autowired
        private ThriftConfigurer thriftConfigurer;

        @Override
        protected String getDescription() {
            return "Thrift services";
        }

        @Override
        @SneakyThrows({NoSuchMethodException.class, ClassNotFoundException.class, InstantiationException.class, IllegalAccessException.class})
        protected void register(String description, ServletContext servletContext) {
            for (String beanName : applicationContext.getBeanNamesForAnnotation(ThriftController.class)) {
                ThriftController annotation = applicationContext.findAnnotationOnBean(beanName, ThriftController.class);

                register(servletContext, annotation.value(), protocolFactory.getClass(), applicationContext.getBean(beanName));
            }
        }

        protected void register(ServletContext servletContext, String[] urls, Class<? extends TProtocolFactory> factory, Object handler) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException {
            Class<?>[] handlerInterfaces = ClassUtils.getAllInterfaces(handler);

            Class ifaceClass = null;
            Class<TProcessor> processorClass = null;
            Class serviceClass = null;

            for (Class<?> handlerInterfaceClass : handlerInterfaces) {
                if (!handlerInterfaceClass.getName().endsWith("$Iface")) {
// Path: autoconfigure/src/main/java/ru/trylogic/spring/boot/thrift/aop/LoggingThriftMethodInterceptor.java
// @Slf4j
// public class LoggingThriftMethodInterceptor implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
// 
//     @Override
//     public void before(Method method, Object[] args, Object target) throws Throwable {
//         log.info("Thrift method {}.{}() is called with args: {}",
//                  target.getClass().getSimpleName(),
//                  method.getName(),
//                  args);
//     }
// 
//     @Override
//     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
//         log.info("Thrift method {}.{}() returns this: {}",
//                  target.getClass().getSimpleName(),
//                  method.getName(),
//                  returnValue);
//     }
// 
//     @SuppressWarnings("unused")
//     public void afterThrowing(Method method, Object[] args, Object target, Exception e) throws Throwable {
//         if (!(e instanceof TException)) {
//             log.warn("Unexpected exception in " + target.getClass().getCanonicalName() + "." + method.getName(), e);
//             throw new TApplicationException(TApplicationException.INTERNAL_ERROR, e.toString());
//         }
//         log.warn("Exception in Thrift method {}.{}() when called with args: {}", target.getClass().getSimpleName(), method.getName(), args, e);
//     }
// }
// 
// Path: autoconfigure/src/main/java/ru/trylogic/spring/boot/thrift/aop/MetricsThriftMethodInterceptor.java
// @RequiredArgsConstructor
// public class MetricsThriftMethodInterceptor implements MethodInterceptor {
//     private static final String THRIFT_REQUEST_DURATION_METRIC = "thrift.request.duration";
// 
//     private final MeterRegistry meterRegistry;
// 
//     @Override
//     public Object invoke(MethodInvocation invocation) throws Throwable {
//         final long startTime = System.nanoTime();
// 
//         try {
//             final Object result = invocation.proceed();
//             time(invocation, startTime);
//             return result;
//         } catch (Exception e) {
//             time(invocation, startTime, e);
//             throw e;
//         } finally {
//             meterRegistry.gauge("timer.thrift." + invocation.getThis().getClass().getCanonicalName() + "." + invocation.getMethod().getName(), System.currentTimeMillis() - startTime);
//         }
//     }
// 
//     private void time(MethodInvocation invocation, long startTime) {
//         time(invocation, startTime, null);
//     }
// 
//     private void time(MethodInvocation invocation, long startTime, Exception exception) {
//         Timer
//                 .builder(THRIFT_REQUEST_DURATION_METRIC)
//                 .publishPercentileHistogram()
//                 .description("Thrift handler request duration")
//                 .tags(
//                         "handler", invocation.getThis().getClass().getCanonicalName(),
//                         "method", invocation.getMethod().getName(),
//                         "status", exception == null ? "ok" : exception.getClass().getCanonicalName()
//                 )
//                 .register(meterRegistry)
//                 .record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
//     }
// }


// Path: autoconfigure/src/main/java/ru/trylogic/spring/boot/thrift/ThriftAutoConfiguration.java
import io.micrometer.core.instrument.MeterRegistry;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.RegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import ru.trylogic.spring.boot.thrift.annotation.ThriftController;
import ru.trylogic.spring.boot.thrift.aop.LoggingThriftMethodInterceptor;
import ru.trylogic.spring.boot.thrift.aop.MetricsThriftMethodInterceptor;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import java.lang.reflect.Constructor;

        @Autowired
        private TProtocolFactory protocolFactory;

        @Autowired
        private ThriftConfigurer thriftConfigurer;

        @Override
        protected String getDescription() {
            return "Thrift services";
        }

        @Override
        @SneakyThrows({NoSuchMethodException.class, ClassNotFoundException.class, InstantiationException.class, IllegalAccessException.class})
        protected void register(String description, ServletContext servletContext) {
            for (String beanName : applicationContext.getBeanNamesForAnnotation(ThriftController.class)) {
                ThriftController annotation = applicationContext.findAnnotationOnBean(beanName, ThriftController.class);

                register(servletContext, annotation.value(), protocolFactory.getClass(), applicationContext.getBean(beanName));
            }
        }

        protected void register(ServletContext servletContext, String[] urls, Class<? extends TProtocolFactory> factory, Object handler) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException {
            Class<?>[] handlerInterfaces = ClassUtils.getAllInterfaces(handler);

            Class ifaceClass = null;
            Class<TProcessor> processorClass = null;
            Class serviceClass = null;

            for (Class<?> handlerInterfaceClass : handlerInterfaces) {
                if (!handlerInterfaceClass.getName().endsWith("$Iface")) {
                    continue;