/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed 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.guvnor.ala.util; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.InvocationHandlerAdapter; import net.bytebuddy.matcher.ElementMatchers; import org.apache.commons.beanutils.PropertyUtilsBean; import org.apache.commons.configuration.interpol.ConfigurationInterpolator; import org.apache.commons.lang.text.StrLookup; import org.apache.commons.lang.text.StrSubstitutor; import org.guvnor.ala.config.CloneableConfig; /** * This class deals with Variable Interpolations inside pipelines. * It uses bytebuddy to create intermediary types for the results * created by the interpolation process */ public final class VariableInterpolation { private VariableInterpolation() { } private static final ConfigurationInterpolator interpolator = new ConfigurationInterpolator(); private static final StrSubstitutor substitutor = new StrSubstitutor( interpolator ); public static <T> T interpolate( final Map<String, Object> values, final T object ) { interpolator.setDefaultLookup( new MapOfMapStrLookup( values ) ); return proxy( object ); } private static class MapOfMapStrLookup extends StrLookup { private final Map map; MapOfMapStrLookup( Map map ) { this.map = map; } @Override public String lookup( String key ) { if ( this.map == null ) { return null; } else { int dotIndex = key.indexOf( "." ); Object obj = this.map.get( key.substring( 0, dotIndex < 0 ? key.length() : dotIndex ) ); if ( obj instanceof Map ) { return new MapOfMapStrLookup( ( (Map) obj ) ).lookup( key.substring( key.indexOf( "." ) + 1 ) ); } else if ( obj != null && !( obj instanceof String ) && key.contains( "." ) ) { final String subkey = key.substring( key.indexOf( "." ) + 1 ); for ( PropertyDescriptor descriptor : new PropertyUtilsBean().getPropertyDescriptors( obj ) ) { if ( descriptor.getName().equals( subkey ) ) { try { return descriptor.getReadMethod().invoke( obj ).toString(); } catch ( Exception ex ) { continue; } } } } return obj == null ? "" : obj.toString(); } } } public static <T> T proxy( final T instance ) { try { Class<?>[] _interfaces; Class<?> currentClass = instance.getClass(); do { _interfaces = currentClass.getInterfaces(); currentClass = currentClass.getSuperclass(); } while ( _interfaces.length == 0 && currentClass != null ); T result = (T) new ByteBuddy() .subclass( Object.class ) .implement( _interfaces ) .method( ElementMatchers.any() ) .intercept( InvocationHandlerAdapter.of( new InterpolationHandler( instance ) ) ) .make() .load( instance.getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION ) .getLoaded() .newInstance(); if ( instance instanceof CloneableConfig ) { return (T) ( (CloneableConfig) result ).asNewClone( result ); } return result; } catch ( final Exception ignored ) { ignored.printStackTrace(); return instance; } } public static class InterpolationHandler implements InvocationHandler { Object object; public InterpolationHandler( final Object object ) { this.object = object; } @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { Object result = method.invoke( object, args ); if ( result != null && result instanceof String ) { return substitutor.replace( (String) result ); } else { return result; } } } }