/* * Copyright 2016 LINE Corporation * * LINE Corporation 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 com.linecorp.armeria.internal.thrift; import static java.util.Objects.requireNonNull; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.thrift.AsyncProcessFunction; import org.apache.thrift.ProcessFunction; import org.apache.thrift.TBaseAsyncProcessor; import org.apache.thrift.TBaseProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.linecorp.armeria.internal.Types; /** * Provides the metadata of a Thrift service interface or implementation. */ public final class ThriftServiceMetadata { private static final Logger logger = LoggerFactory.getLogger(ThriftServiceMetadata.class); private final Set<Class<?>> interfaces; /** * A map whose key is a method name and whose value is {@link AsyncProcessFunction} or * {@link ProcessFunction}. */ private final Map<String, ThriftFunction> functions = new HashMap<>(); /** * Creates a new instance from a Thrift service implementation that implements one or more Thrift service * interfaces. */ public ThriftServiceMetadata(Object implementation) { requireNonNull(implementation, "implementation"); interfaces = init(implementation); } /** * Creates a new instance from a single Thrift service interface. */ public ThriftServiceMetadata(Class<?> serviceType) { requireNonNull(serviceType, "serviceType"); interfaces = init(null, Collections.singleton(serviceType)); } private Set<Class<?>> init(Object implementation) { return init(implementation, Types.getAllInterfaces(implementation.getClass())); } private Set<Class<?>> init(Object implementation, Iterable<Class<?>> candidateInterfaces) { // Build the map of method names and their corresponding process functions. final Set<String> methodNames = new HashSet<>(); final Set<Class<?>> interfaces = new HashSet<>(); for (Class<?> iface : candidateInterfaces) { final Map<String, AsyncProcessFunction<?, ?, ?>> asyncProcessMap; asyncProcessMap = getThriftAsyncProcessMap(implementation, iface); if (asyncProcessMap != null) { asyncProcessMap.forEach( (name, func) -> registerFunction(methodNames, iface, name, func)); interfaces.add(iface); } final Map<String, ProcessFunction<?, ?>> processMap; processMap = getThriftProcessMap(implementation, iface); if (processMap != null) { processMap.forEach( (name, func) -> registerFunction(methodNames, iface, name, func)); interfaces.add(iface); } } if (functions.isEmpty()) { if (implementation != null) { throw new IllegalArgumentException('\'' + implementation.getClass().getName() + "' is not a Thrift service implementation."); } else { throw new IllegalArgumentException("not a Thrift service interface: " + candidateInterfaces); } } return Collections.unmodifiableSet(interfaces); } private static Map<String, ProcessFunction<?, ?>> getThriftProcessMap(Object service, Class<?> iface) { final String name = iface.getName(); if (!name.endsWith("$Iface")) { return null; } final String processorName = name.substring(0, name.length() - 5) + "Processor"; try { final Class<?> processorClass = Class.forName(processorName, false, iface.getClassLoader()); if (!TBaseProcessor.class.isAssignableFrom(processorClass)) { return null; } final Constructor<?> processorConstructor = processorClass.getConstructor(iface); @SuppressWarnings("rawtypes") final TBaseProcessor processor = (TBaseProcessor) processorConstructor.newInstance(service); @SuppressWarnings("unchecked") Map<String, ProcessFunction<?, ?>> processMap = (Map<String, ProcessFunction<?, ?>>) processor.getProcessMapView(); return processMap; } catch (Exception e) { logger.debug("Failed to retrieve the process map from: {}", iface, e); return null; } } private static Map<String, AsyncProcessFunction<?, ?, ?>> getThriftAsyncProcessMap( Object service, Class<?> iface) { final String name = iface.getName(); if (!name.endsWith("$AsyncIface")) { return null; } final String processorName = name.substring(0, name.length() - 10) + "AsyncProcessor"; try { Class<?> processorClass = Class.forName(processorName, false, iface.getClassLoader()); if (!TBaseAsyncProcessor.class.isAssignableFrom(processorClass)) { return null; } final Constructor<?> processorConstructor = processorClass.getConstructor(iface); @SuppressWarnings("rawtypes") final TBaseAsyncProcessor processor = (TBaseAsyncProcessor) processorConstructor.newInstance(service); @SuppressWarnings("unchecked") Map<String, AsyncProcessFunction<?, ?, ?>> processMap = (Map<String, AsyncProcessFunction<?, ?, ?>>) processor.getProcessMapView(); return processMap; } catch (Exception e) { logger.debug("Failed to retrieve the asynchronous process map from:: {}", iface, e); return null; } } @SuppressWarnings("rawtypes") private void registerFunction(Set<String> methodNames, Class<?> iface, String name, Object func) { checkDuplicateMethodName(methodNames, name); methodNames.add(name); try { final ThriftFunction f; if (func instanceof ProcessFunction) { f = new ThriftFunction(iface, (ProcessFunction) func); } else { f = new ThriftFunction(iface, (AsyncProcessFunction) func); } functions.put(name, f); } catch (Exception e) { throw new IllegalArgumentException("failed to retrieve function metadata: " + iface.getName() + '.' + name + "()", e); } } private static void checkDuplicateMethodName(Set<String> methodNames, String name) { if (methodNames.contains(name)) { throw new IllegalArgumentException("duplicate Thrift method name: " + name); } } /** * Returns the Thrift service interfaces implemented. */ public Set<Class<?>> interfaces() { return interfaces; } /** * Returns the {@link ThriftFunction} that provides the metadata of the specified Thrift function. * * @return the {@link ThriftFunction}. {@code null} if there's no such function. */ public ThriftFunction function(String method) { return functions.get(method); } }