/* * -----------------------------------------------------------------------\ * PerfCake *   * Copyright (C) 2010 - 2016 the original author or authors. *   * 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.perfcake.util; import org.perfcake.PerfCakeConst; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.BeanUtilsBean; import org.apache.commons.beanutils.ConvertUtilsBean; import org.apache.commons.beanutils.FluentPropertyBeanIntrospector; import org.apache.commons.beanutils.PropertyUtilsBean; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.w3c.dom.Element; import java.io.File; import java.io.FilenameFilter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Map; import java.util.Properties; /** * Creates POJOs according to the given class name and a map of attributes and their values. * * @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a> */ public class ObjectFactory { /** * Logger of this class. */ private static final Logger log = LogManager.getLogger(ObjectFactory.class); /** * Cached plugin class loader. */ private static ClassLoader pluginClassLoader = null; /** * There should be no instance of a utility class. */ private ObjectFactory() { } /** * Looks up for a set method on a bean that is able to accept Element * * @param object * The object on which we search for the setter. * @param propertyName * Name of the property of type Element. * @param value * Value to be set to the property. * @return <code>true</code> if operation has succeeded, <code>false</code> otherwise * @throws InvocationTargetException * When it was not possible to call the setter on the object. * @throws IllegalAccessException * When we did not have the correct rights to set any of the properties. */ private static boolean setElementProperty(final Object object, final String propertyName, final Element value) throws InvocationTargetException, IllegalAccessException { try { final Method setter = object.getClass().getDeclaredMethod("set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1) + "AsElement", Element.class); setter.invoke(object, value); return true; } catch (final NoSuchMethodException e) { return false; } } /** * Sets the attributes of an object according to the properties provided. * * @param object * Object on which the properties should be set. * @param properties * Properties that should be set as properties of the object. Key is a name of an object property and value is its value. * @throws InvocationTargetException * When it was not possible to call the setter on the object. * @throws IllegalAccessException * When we did not have the correct rights to set any of the properties. */ public static void setPropertiesOnObject(final Object object, final Properties properties) throws IllegalAccessException, InvocationTargetException { final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); propertyUtilsBean.addBeanIntrospector(new FluentPropertyBeanIntrospector()); final BeanUtilsBean beanUtilsBean = new BeanUtilsBean(new EnumConvertUtilsBean(), propertyUtilsBean); if (log.isTraceEnabled()) { log.trace(String.format("Setting properties on an instance of %s.", object.getClass().getName())); } for (final Map.Entry<Object, Object> entry : properties.entrySet()) { if (log.isTraceEnabled()) { log.trace("Setting property: '" + entry.getKey().toString() + "'='" + entry.getValue().toString() + "'"); } boolean successSet = false; // did we manage to set the property value? if (entry.getValue() instanceof Element) { // first, is it an XML element? try to set it... successSet = setElementProperty(object, entry.getKey().toString(), (Element) entry.getValue()); } if (!successSet) { // not yet set - either it was not an XML element or it failed with it beanUtilsBean.setProperty(object, entry.getKey().toString(), entry.getValue()); try { beanUtilsBean.getProperty(object, entry.getKey().toString()); } catch (ReflectiveOperationException reo) { log.warn(String.format("It was not possible to reliably configure property %s on class %s. " + "You may have a mistake in the scenario, or the class does not allow reading of the property.", entry.getKey().toString(), object.getClass().getCanonicalName())); } } } } /** * Creates an instance of the given class and configures its properties. * * @param className * Name of the class to be constructed. * @param properties * Properties to be configured on the class instance. * @return Configured class instance. * @throws InstantiationException * When it was not possible to create the object instance. * @throws IllegalAccessException * When we did not have correct rights to create the object or set any of its properties. * @throws ClassNotFoundException * When the given class does not exists. * @throws InvocationTargetException * When it was not possible to call any of the properties setters. */ public static Object summonInstance(final String className, final Properties properties) throws InstantiationException, IllegalAccessException, ClassNotFoundException, InvocationTargetException { if (log.isTraceEnabled()) { log.trace(String.format("Summoning a new instance of class '%s'.", className)); } final Object object = Class.forName(className, false, getPluginClassLoader()).newInstance(); setPropertiesOnObject(object, properties); return object; } /** * Gets the properties of an object as a {@link java.util.Properties} object. * * @param object * The object to be inspected. * @return All the properties of the object. * @throws IllegalAccessException * When it was not possible to find, call, or use any of the getter methods. * @throws NoSuchMethodException * When it was not possible to find, call, or use any of the getter methods. * @throws InvocationTargetException * When it was not possible to find, call, or use any of the getter methods. */ public static Properties getObjectProperties(final Object object) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { final Properties properties = new Properties(); final Map<String, String> attributes = BeanUtils.describe(object); attributes.forEach((key, value) -> { if (value != null) { properties.put(key, value); } }); return properties; } /** * Gets a dedicated class loader for loading plugins. * * @return Plugin class loader. */ protected static ClassLoader getPluginClassLoader() { if (pluginClassLoader == null) { final ClassLoader currentClassLoader = ObjectFactory.class.getClassLoader(); final String pluginsDirProp = Utils.getProperty(PerfCakeConst.PLUGINS_DIR_PROPERTY); if (pluginsDirProp == null) { pluginClassLoader = currentClassLoader; return pluginClassLoader; } final File pluginsDir = new File(pluginsDirProp); final File[] plugins = pluginsDir.listFiles(new FileExtensionFilter(".jar")); if (plugins == null) { if (log.isWarnEnabled()) { log.warn("Plugin directory (" + pluginsDir + ") is invalid. Skipping plugins loading."); } pluginClassLoader = currentClassLoader; return pluginClassLoader; } if (plugins.length == 0) { pluginClassLoader = currentClassLoader; return pluginClassLoader; } for (final File f : plugins) { log.info("Recognized plugin library " + f.getName()); } final URL[] pluginUrls = new URL[plugins.length]; for (int i = 0; i < plugins.length; i++) { try { pluginUrls[i] = plugins[i].toURI().toURL(); } catch (final MalformedURLException e) { log.warn(String.format("Cannot resolve path to plugin '%s', skipping this file", plugins[i])); } } AccessController.doPrivileged((PrivilegedAction<Void>) () -> { pluginClassLoader = new URLClassLoader(pluginUrls, currentClassLoader); return null; }); } return pluginClassLoader; } private static class EnumConvertUtilsBean extends ConvertUtilsBean { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Object convert(final String value, final Class clazz) { if (clazz.isEnum()) { return Enum.valueOf(clazz, Utils.camelCaseToEnum(value)); } else { return super.convert(value, clazz); } } } private static class FileExtensionFilter implements FilenameFilter { private final String extension; public FileExtensionFilter(final String extension) { this.extension = extension; } public boolean accept(final File dir, final String name) { return name.endsWith(extension); } } }