All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xwiki.properties.internal.DefaultBeanManager Maven / Gradle / Ivy

There is a newer version: 16.8.0-rc-1
Show newest version
/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.properties.internal;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.ConstraintViolation;
import javax.validation.NoProviderFoundException;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.slf4j.Logger;
import org.xwiki.collection.SoftCache;
import org.xwiki.component.annotation.Component;
import org.xwiki.properties.BeanDescriptor;
import org.xwiki.properties.BeanManager;
import org.xwiki.properties.ConverterManager;
import org.xwiki.properties.PropertyDescriptor;
import org.xwiki.properties.PropertyException;
import org.xwiki.properties.PropertyMandatoryException;
import org.xwiki.properties.RawProperties;

/**
 * Default implementation of {@link BeanManager} component.
 * 
    *
  • use hibernate-validator implementation of JSR 303
  • *
  • add support for any Enum conversion. See {@link org.xwiki.properties.internal.converter.EnumConverter}.
  • *
  • add support for {@link java.awt.Color} conversion using "r,g,b" or "#xxxxxx" format. See * {@link org.xwiki.properties.internal.converter.ColorConverter}.
  • *
* * @version $Id: fdaf32ee85126e9442bb8dcc0d60096192eb3ccc $ * @since 2.0M2 */ @Component @Singleton public class DefaultBeanManager implements BeanManager { /** * Cache the already parsed classes. We store weak reference since the classes might come from extensions later * uninstalled/upgraded or from scripts. */ private SoftCache, BeanDescriptor> beanDescriptorCache = new SoftCache<>(); /** * The logger to use for logging. */ @Inject private Logger logger; /** * The {@link ConverterManager} component. */ @Inject private ConverterManager converterManager; /** * The factory to use to get new JSR 303 validators. */ private ValidatorFactory validatorFactory; /** * @return the factory to use to get new JSR 303 validators. */ public ValidatorFactory getValidatorFactory() { if (this.validatorFactory == null) { try { this.validatorFactory = Validation.buildDefaultValidatorFactory(); } catch (NoProviderFoundException e) { this.logger.debug("Unable to find a Bean Validation provider. There will be no Java bean validation.", e); } catch (ValidationException e) { this.logger .error("Failed to build the Bean Validation provider. There will be no Java bean validation.", e); } } return this.validatorFactory; } @Override public void populate(Object bean, Map values) throws PropertyException { Map remainingValues = new HashMap<>(values); // Populate populateBean(bean, remainingValues); // If the bean implements RawProperties, inject remaining properties if (!remainingValues.isEmpty() && bean instanceof RawProperties) { RawProperties rawProperties = (RawProperties) bean; for (Map.Entry entry : remainingValues.entrySet()) { rawProperties.set(entry.getKey(), entry.getValue()); } } // Validate validateBean(bean); } /** * Populate the provided bean with provided values. *

* values is "consumed": when method executing is finished it only contains not populated properties. * * @param bean the java bean to populate * @param values the values to convert and inject in the java bean * @throws PropertyException error when populating the bean */ private void populateBean(Object bean, Map values) throws PropertyException { BeanDescriptor beanDescriptor = getBeanDescriptor(bean.getClass()); // Lower case provided properties to easily ignore properties name case Map lowerKeyMap = new HashMap<>(values.size()); for (Map.Entry entry : values.entrySet()) { lowerKeyMap.put(entry.getKey().toLowerCase(), entry.getKey()); } for (PropertyDescriptor propertyDescriptor : beanDescriptor.getProperties()) { String propertyId = propertyDescriptor.getId(); Object value = values.get(propertyId); if (value == null) { propertyId = propertyId.toLowerCase(); value = values.get(lowerKeyMap.get(propertyId)); } if (value != null) { try { // Convert Object convertedValue = this.converterManager.convert(propertyDescriptor.getPropertyType(), value); if (propertyDescriptor.getWriteMethod() != null) { Method writerMethod = propertyDescriptor.getWriteMethod(); setAccessibleSafely(writerMethod); // Invoke the method writerMethod.invoke(bean, convertedValue); } else if (propertyDescriptor.getField() != null) { Field field = propertyDescriptor.getField(); setAccessibleSafely(field); // Set the field field.set(bean, convertedValue); } } catch (Exception e) { throw new PropertyException("Failed to populate property [" + propertyId + "]", e); } // "Tick" already populated properties values.remove(propertyId); } else if (propertyDescriptor.isMandatory()) { throw new PropertyMandatoryException(propertyId); } } } /** * Support nested private classes with public setters. Workaround for * java reflections bug JDK-4533479. * * @param classMember the class member to make accessible. */ private void setAccessibleSafely(AccessibleObject classMember) { try { // We do this in a try/catch to avoid false positives caused by existing SecurityManagers. classMember.setAccessible(true); } catch (SecurityException se) { logger.debug("Failed to call setAccessible for [{}]", classMember, se); } } /** * Validate populated values based on JSR 303. * * @param bean the bean to validate * @throws PropertyException validation error */ private void validateBean(Object bean) throws PropertyException { if (getValidatorFactory() != null) { Validator validator = getValidatorFactory().getValidator(); Set> constraintViolations = validator.validate(bean); if (!constraintViolations.isEmpty()) { throw new PropertyException( "Failed to validate bean: [" + constraintViolations.iterator().next().getMessage() + "]"); } } } @Override public BeanDescriptor getBeanDescriptor(Class beanClass) { BeanDescriptor beanDescriptor = null; if (beanClass != null) { // Get the bean descriptor from the cache beanDescriptor = this.beanDescriptorCache.get(beanClass); // Create a new one if none could be found if (beanDescriptor == null) { beanDescriptor = new DefaultBeanDescriptor(beanClass); this.beanDescriptorCache.put(beanClass, beanDescriptor); } } return beanDescriptor; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy