de.metas.ui.web.process.adprocess.WebuiProcessClassInfo.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.process.adprocess.WebuiProcessClassInfo.java

Source

package de.metas.ui.web.process.adprocess;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import org.adempiere.exceptions.AdempiereException;
import org.reflections.ReflectionUtils;
import org.slf4j.Logger;

import com.google.common.base.MoreObjects;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import de.metas.logging.LogManager;
import de.metas.process.JavaProcess;
import de.metas.process.ProcessClassInfo;
import de.metas.ui.web.process.descriptor.ProcessParamLookupValuesProvider;
import de.metas.ui.web.window.datatypes.LookupValuesList;
import de.metas.ui.web.window.datatypes.PanelLayoutType;
import de.metas.ui.web.window.descriptor.ListLookupDescriptor;
import de.metas.ui.web.window.descriptor.LookupDescriptor;
import de.metas.ui.web.window.descriptor.LookupDescriptorProvider;
import de.metas.ui.web.window.model.lookup.LookupDataSourceContext;
import de.metas.util.Check;
import de.metas.util.GuavaCollectors;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2017 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

/**
 * Decorates a {@link ProcessClassInfo} and contains webui related informations too.
 * 
 * @author metas-dev <dev@metasfresh.com>
 *
 */
final class WebuiProcessClassInfo {
    public static final WebuiProcessClassInfo of(@Nullable final Class<?> processClass) {
        if (processClass == null) {
            return WebuiProcessClassInfo.NULL;
        }
        return cache.getUnchecked(processClass);
    }

    public static final WebuiProcessClassInfo of(@Nullable final String processClassname) {
        if (Check.isEmpty(processClassname, true)) {
            return NULL;
        }

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            classLoader = WebuiProcessClassInfo.class.getClassLoader();
        }

        try {
            final Class<?> processClass = classLoader.loadClass(processClassname.trim());
            return of(processClass);
        } catch (final ClassNotFoundException e) {
            logger.info("Could not load process class for {}. IGNORED", processClassname);
            return NULL;
        }
    }

    private static final Logger logger = LogManager.getLogger(WebuiProcessClassInfo.class);

    /** "Process class" to {@link WebuiProcessClassInfo} cache */
    private static final LoadingCache<Class<?>, WebuiProcessClassInfo> cache = CacheBuilder.newBuilder().weakKeys() // to prevent ClassLoader memory leaks nightmare
            .build(new CacheLoader<Class<?>, WebuiProcessClassInfo>() {
                @Override
                public WebuiProcessClassInfo load(final Class<?> processClass) {
                    try {
                        return createWebuiProcessClassInfo(processClass);
                    } catch (final Throwable e) {
                        logger.error("Failed introspecting process class info: {}. Fallback to defaults: {}",
                                processClass, NULL, e);
                        return NULL;
                    }
                }
            });

    private static WebuiProcessClassInfo createWebuiProcessClassInfo(final Class<?> processClass) throws Exception {
        final ProcessClassInfo processClassInfo = ProcessClassInfo.of(processClass);

        final WebuiProcess webuiProcessAnn = processClass.getAnnotation(WebuiProcess.class);

        @SuppressWarnings("unchecked")
        final Set<Method> lookupValuesProviderMethods = ReflectionUtils.getAllMethods(processClass,
                ReflectionUtils.withAnnotation(ProcessParamLookupValuesProvider.class));
        final ImmutableMap<String, LookupDescriptorProvider> paramLookupValuesProviders = lookupValuesProviderMethods
                .stream().map(method -> createParamLookupValuesProvider(method))
                .collect(GuavaCollectors.toImmutableMap());

        //
        // Check is there were no settings at all so we could return our NULL instance
        if (ProcessClassInfo.isNull(processClassInfo) && paramLookupValuesProviders.isEmpty()) {
            return NULL;
        }

        return new WebuiProcessClassInfo(processClassInfo, webuiProcessAnn, paramLookupValuesProviders);
    }

    private static final WebuiProcessClassInfo NULL = new WebuiProcessClassInfo();

    private final ProcessClassInfo processClassInfo;
    private final ImmutableMap<String, LookupDescriptorProvider> paramLookupValuesProviders;
    private final PanelLayoutType layoutType;

    /** Null constructor */
    private WebuiProcessClassInfo() {
        processClassInfo = ProcessClassInfo.NULL;
        paramLookupValuesProviders = ImmutableMap.of();
        this.layoutType = PanelLayoutType.Panel;
    }

    private WebuiProcessClassInfo(final ProcessClassInfo processClassInfo, final WebuiProcess webuiProcessAnn,
            final ImmutableMap<String, LookupDescriptorProvider> paramLookupValuesProviders) {
        this.processClassInfo = processClassInfo;
        this.paramLookupValuesProviders = paramLookupValuesProviders;
        if (webuiProcessAnn != null) {
            this.layoutType = webuiProcessAnn.layoutType();
        } else {
            this.layoutType = PanelLayoutType.Panel;
        }
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("paramLookupValuesProviders", paramLookupValuesProviders)
                .add("processClassInfo", processClassInfo).toString();
    }

    public PanelLayoutType getLayoutType() {
        return layoutType;
    }

    public LookupDescriptorProvider getLookupDescriptorProviderOrNull(final String parameterName) {
        return paramLookupValuesProviders.get(parameterName);
    }

    public boolean isForwardValueToJavaProcessInstance(final String parameterName) {
        return !processClassInfo.getParameterInfos(parameterName).isEmpty();
    }

    //
    //
    // ----
    //
    //

    /** @return parameterName and provider */
    private static Map.Entry<String, LookupDescriptorProvider> createParamLookupValuesProvider(
            final Method method) {
        final ProcessParamLookupValuesProvider ann = method.getAnnotation(ProcessParamLookupValuesProvider.class);

        if (!LookupValuesList.class.isAssignableFrom(method.getReturnType())) {
            throw new AdempiereException("Method's return type shall be " + LookupValuesList.class + ": " + method);
        }

        final ImmutableList<Function<LookupDataSourceContext, Object>> parameterValueProviders = Stream
                .of(method.getParameterTypes()).map(parameterType -> {
                    final Function<LookupDataSourceContext, Object> parameterValueProvider;
                    if (LookupDataSourceContext.class.isAssignableFrom(parameterType)) {
                        parameterValueProvider = evalCtx -> evalCtx;
                    } else {
                        throw new AdempiereException("Parameter " + parameterType + " not supported for " + method);
                    }
                    return parameterValueProvider;
                }).collect(ImmutableList.toImmutableList());

        final Method methodToInvoke = method; // FIXME: holding a hard reference to method may introduce ClassLoader memory leaks

        final LookupDescriptor lookupDescriptor = ListLookupDescriptor.builder()
                .setLookupTableName(ann.lookupTableName()).setDependsOnFieldNames(ann.dependsOn())
                .setLookupSourceType(ann.lookupSource()).setLookupValues(ann.numericKey(),
                        evalCtx -> retriveLookupValues(methodToInvoke, parameterValueProviders, evalCtx))
                .build();

        final LookupDescriptorProvider lookupDescriptorProvider = LookupDescriptorProvider
                .singleton(lookupDescriptor);
        return GuavaCollectors.entry(ann.parameterName(), lookupDescriptorProvider);
    }

    private static final LookupValuesList retriveLookupValues(final Method method,
            final List<Function<LookupDataSourceContext, Object>> parameterValueProviders,
            final LookupDataSourceContext evalCtx) {
        Check.assumeNotNull(method, "Parameter method is not null");
        final JavaProcess processClassInstance = JavaProcess.currentInstance();

        final Object[] methodParams = parameterValueProviders.stream()
                .map(paramValueProvider -> paramValueProvider.apply(evalCtx)).toArray();

        try {
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }

            final LookupValuesList lookupValues = (LookupValuesList) method.invoke(processClassInstance,
                    methodParams);
            return lookupValues;
        } catch (IllegalAccessException | InvocationTargetException e) {
            final Throwable cause = AdempiereException.extractCause(e);
            throw new AdempiereException("Failed invoking " + method + " using " + methodParams, cause);
        } catch (final Exception e) {
            throw AdempiereException.wrapIfNeeded(e);
        }
    }
}