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

org.milyn.javabean.BeanInstancePopulator Maven / Gradle / Ivy

There is a newer version: 1.7.1
Show newest version
/*
	Milyn - Copyright (C) 2006 - 2010

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License (version 2.1) as published by the Free Software
	Foundation.

	This library 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:
	http://www.gnu.org/licenses/lgpl.txt
*/
package org.milyn.javabean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.milyn.SmooksException;
import org.milyn.delivery.Fragment;
import org.milyn.javabean.lifecycle.BeanContextLifecycleEvent;
import org.milyn.javabean.lifecycle.BeanLifecycle;
import org.milyn.util.CollectionsUtil;
import org.milyn.util.ClassUtil;
import org.milyn.cdr.SmooksConfigurationException;
import org.milyn.cdr.annotation.AnnotationConstants;
import org.milyn.cdr.annotation.AppContext;
import org.milyn.cdr.annotation.ConfigParam;
import org.milyn.container.ApplicationContext;
import org.milyn.container.ExecutionContext;
import org.milyn.delivery.annotation.Initialize;
import org.milyn.delivery.dom.DOMElementVisitor;
import org.milyn.delivery.sax.*;
import org.milyn.delivery.ordering.Producer;
import org.milyn.delivery.ordering.Consumer;
import org.milyn.event.report.annotation.VisitAfterReport;
import org.milyn.event.report.annotation.VisitBeforeReport;
import org.milyn.expression.MVELExpressionEvaluator;
import org.milyn.javabean.BeanRuntimeInfo.Classification;
import org.milyn.javabean.observers.ListToArrayChangeObserver;
import org.milyn.javabean.observers.BeanWiringObserver;
import org.milyn.javabean.repository.BeanId;
import org.milyn.javabean.context.BeanContext;
import org.milyn.javabean.context.BeanIdStore;
import org.milyn.javabean.decoders.PreprocessDecoder;
import org.milyn.javabean.decoders.StringDecoder;
import org.milyn.xml.DomUtils;
import org.milyn.xml.NamespaceMappings;
import org.w3c.dom.Element;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Bean instance populator visitor class.
 * 

* Targeted via {@link org.milyn.javabean.BeanPopulator} expansion configuration. * * @author [email protected] * @author [email protected] */ @VisitBeforeReport(condition = "parameters.containsKey('wireBeanId') || parameters.containsKey('valueAttributeName')", summary = "<#if resource.parameters.wireBeanId??>Create bean lifecycle observer for bean ${resource.parameters.wireBeanId}." + "<#else>Populating ${resource.parameters.beanId} with the value from the attribute ${resource.parameters.valueAttributeName}.", detailTemplate = "reporting/BeanInstancePopulatorReport_Before.html") @VisitAfterReport(condition = "!parameters.containsKey('wireBeanId') && !parameters.containsKey('valueAttributeName')", summary = "Populating ${resource.parameters.beanId} with a value from this element.", detailTemplate = "reporting/BeanInstancePopulatorReport_After.html") public class BeanInstancePopulator implements DOMElementVisitor, SAXVisitBefore, SAXVisitAfter, Producer, Consumer { private static final Log logger = LogFactory.getLog(BeanInstancePopulator.class); private static final String EXPRESSION_VALUE_VARIABLE_NAME = "_VALUE"; public static final String VALUE_ATTRIBUTE_NAME = "valueAttributeName"; public static final String VALUE_ATTRIBUTE_PREFIX = "valueAttributePrefix"; public static final String NOTIFY_POPULATE = "org.milyn.javabean.notify.populate"; private String id; @ConfigParam(name="beanId") private String beanIdName; @ConfigParam(name="wireBeanId", defaultVal = AnnotationConstants.NULL_STRING) private String wireBeanIdName; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private Class wireBeanType; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private Class wireBeanAnnotation; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private String expression; private MVELExpressionEvaluator expressionEvaluator; private boolean expressionHasDataVariable = false; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private String property; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private String setterMethod; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private String valueAttributeName; @ConfigParam(defaultVal = AnnotationConstants.NULL_STRING) private String valueAttributePrefix; private String valueAttributeNS; @ConfigParam(name="type", defaultVal = AnnotationConstants.NULL_STRING) private String typeAlias; @ConfigParam(name="default", defaultVal = AnnotationConstants.NULL_STRING) private String defaultVal; @ConfigParam(name= NOTIFY_POPULATE, defaultVal = "false") private boolean notifyPopulate; @AppContext private ApplicationContext appContext; private BeanIdStore beanIdStore; private BeanId beanId; private BeanId wireBeanId; private BeanRuntimeInfo beanRuntimeInfo; private BeanRuntimeInfo wiredBeanRuntimeInfo; private Method propertySetterMethod; private boolean checkedForSetterMethod; private boolean isAttribute = true; private DataDecoder decoder; private String mapKeyAttribute; private boolean isBeanWiring; private BeanWiringObserver wireByBeanIdObserver; private ListToArrayChangeObserver listToArrayChangeObserver; public void setBeanId(String beanId) { this.beanIdName = beanId; } public void setWireBeanId(String wireBeanId) { this.wireBeanIdName = wireBeanId; } public void setExpression(MVELExpressionEvaluator expression) { this.expressionEvaluator = expression; } public void setProperty(String property) { this.property = property; } public void setSetterMethod(String setterMethod) { this.setterMethod = setterMethod; } public void setValueAttributeName(String valueAttributeName) { this.valueAttributeName = valueAttributeName; } public void setValueAttributePrefix(String valueAttributePrefix) { this.valueAttributePrefix = valueAttributePrefix; } public void setTypeAlias(String typeAlias) { this.typeAlias = typeAlias; } public void setDecoder(DataDecoder decoder) { this.decoder = decoder; } public void setDefaultVal(String defaultVal) { this.defaultVal = defaultVal; } /** * Set the resource configuration on the bean populator. * @throws SmooksConfigurationException Incorrectly configured resource. */ @Initialize public void initialize() throws SmooksConfigurationException { buildId(); beanRuntimeInfo = BeanRuntimeInfo.getBeanRuntimeInfo(beanIdName, appContext); isBeanWiring = (wireBeanIdName != null || wireBeanType != null || wireBeanAnnotation != null); isAttribute = (valueAttributeName != null); if(valueAttributePrefix != null) { Properties namespaces = NamespaceMappings.getMappings(appContext); valueAttributeNS = namespaces.getProperty(valueAttributePrefix); } beanIdStore = appContext.getBeanIdStore(); beanId = beanIdStore.getBeanId(beanIdName); if (setterMethod == null && property == null ) { if(isBeanWiring && (beanRuntimeInfo.getClassification() == Classification.NON_COLLECTION || beanRuntimeInfo.getClassification() == Classification.MAP_COLLECTION)) { // Default the property name if it's a wiring... property = wireBeanIdName; } else if(beanRuntimeInfo.getClassification() == Classification.NON_COLLECTION){ throw new SmooksConfigurationException("Binding configuration for beanIdName='" + beanIdName + "' must contain " + "either a 'property' or 'setterMethod' attribute definition, unless the target bean is a Collection/Array." + " Bean is type '" + beanRuntimeInfo.getPopulateType().getName() + "'."); } } if(beanRuntimeInfo.getClassification() == Classification.MAP_COLLECTION && property != null) { property = property.trim(); if(property.length() > 1 && property.charAt(0) == '@') { mapKeyAttribute = property.substring(1); } } if(expression != null) { expression = expression.trim(); expressionHasDataVariable = expression.contains(EXPRESSION_VALUE_VARIABLE_NAME); expression = expression.replace("this.", beanIdName + "."); if(expression.startsWith("+=")) { expression = beanIdName + "." + property + " +" + expression.substring(2); } if(expression.startsWith("-=")) { expression = beanIdName + "." + property + " -" + expression.substring(2); } expressionEvaluator = new MVELExpressionEvaluator(); expressionEvaluator.setExpression(expression); // If we can determine the target binding type, tell MVEL. // If there's a decoder (a typeAlias), we define a String var instead and leave decoding // to the decoder... Class bindingType = resolveBindTypeReflectively(); if(bindingType != null) { if(typeAlias != null) { bindingType = String.class; } expressionEvaluator.setToType(bindingType); } } if(wireBeanIdName != null) { wireBeanId = beanIdStore.getBeanId(wireBeanIdName); if(wireBeanId == null) { wireBeanId = beanIdStore.register(wireBeanIdName); } } if(isBeanWiring) { // These observers can be used concurrently across multiple execution contexts... wireByBeanIdObserver = new BeanWiringObserver(beanId, this).watchedBeanId(wireBeanId).watchedBeanType(wireBeanType).watchedBeanAnnotation(wireBeanAnnotation); if(wireBeanId != null) { // List to array change observer only makes sense if wiring by beanId. listToArrayChangeObserver = new ListToArrayChangeObserver(wireBeanId, property, this); } } if(logger.isDebugEnabled()) { logger.debug("Bean Instance Populator created for [" + beanIdName + "]. property=" + property); } } private void buildId() { StringBuilder idBuilder = new StringBuilder(); idBuilder.append(BeanInstancePopulator.class.getName()); idBuilder.append("#"); idBuilder.append(beanIdName); if(property != null) { idBuilder.append("#") .append(property); } if(setterMethod != null) { idBuilder.append("#") .append(setterMethod) .append("()"); } if(wireBeanIdName != null) { idBuilder.append("#") .append(wireBeanIdName); } id = idBuilder.toString(); } public void visitBefore(Element element, ExecutionContext executionContext) throws SmooksException { if(!beanExists(executionContext)) { logger.debug("Cannot bind data onto bean '" + beanId + "' as bean does not exist in BeanContext."); return; } if(isBeanWiring) { bindBeanValue(executionContext, new Fragment(element)); } else if(isAttribute) { // Bind attribute (i.e. selectors with '@' prefix) values on the visitBefore... bindDomDataValue(element, executionContext); } } public void visitAfter(Element element, ExecutionContext executionContext) throws SmooksException { if(!beanExists(executionContext)) { logger.debug("Cannot bind data onto bean '" + beanId + "' as bean does not exist in BeanContext."); return; } if(!isBeanWiring && !isAttribute) { bindDomDataValue(element, executionContext); } } public void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { if(!beanExists(executionContext)) { logger.debug("Cannot bind data onto bean '" + beanId + "' as bean does not exist in BeanContext."); return; } if(isBeanWiring) { bindBeanValue(executionContext, new Fragment(element)); } else if(isAttribute) { // Bind attribute (i.e. selectors with '@' prefix) values on the visitBefore... bindSaxDataValue(element, executionContext); } else if(expressionEvaluator == null || expressionHasDataVariable) { // It's not a wiring, attribute or expression binding => it's the element's text. // Turn on Text Accumulation... element.accumulateText(); } } public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { if(!beanExists(executionContext)) { logger.debug("Cannot bind data onto bean '" + beanId + "' as bean does not exist in BeanContext."); return; } if(!isBeanWiring && !isAttribute) { bindSaxDataValue(element, executionContext); } } private boolean beanExists(ExecutionContext executionContext) { return (executionContext.getBeanContext().getBean(beanId) != null); } private void bindDomDataValue(Element element, ExecutionContext executionContext) { String dataString; if (isAttribute) { if(valueAttributeNS != null) { dataString = DomUtils.getAttributeValue(element, valueAttributeName, valueAttributeNS); } else { dataString = DomUtils.getAttributeValue(element, valueAttributeName); } } else { dataString = DomUtils.getAllText(element, false); } String propertyName; if(mapKeyAttribute != null) { propertyName = DomUtils.getAttributeValue(element, mapKeyAttribute); if(propertyName == null) { propertyName = DomUtils.getName(element); } } else if(property != null) { propertyName = property; } else { propertyName = DomUtils.getName(element); } if(expressionEvaluator != null) { bindExpressionValue(propertyName, dataString, executionContext, new Fragment(element)); } else { decodeAndSetPropertyValue(propertyName, dataString, executionContext, new Fragment(element)); } } private void bindSaxDataValue(SAXElement element, ExecutionContext executionContext) { String propertyName; if(mapKeyAttribute != null) { propertyName = SAXUtil.getAttribute(mapKeyAttribute, element.getAttributes(), null); if(propertyName == null) { propertyName = element.getName().getLocalPart(); } } else if(property != null) { propertyName = property; } else { propertyName = element.getName().getLocalPart(); } String dataString = null; if(expressionEvaluator == null || expressionHasDataVariable) { if (isAttribute) { if(valueAttributeNS != null) { dataString = SAXUtil.getAttribute(valueAttributeNS, valueAttributeName, element.getAttributes(), null); } else { dataString = SAXUtil.getAttribute(valueAttributeName, element.getAttributes(), null); } } else { dataString = element.getTextContent(); } } if(expressionEvaluator != null) { bindExpressionValue(propertyName, dataString, executionContext, new Fragment(element)); } else { decodeAndSetPropertyValue(propertyName, dataString, executionContext, new Fragment(element)); } } private void bindBeanValue(final ExecutionContext executionContext, Fragment source) { final BeanContext beanContext = executionContext.getBeanContext(); Object bean = null; if(wireBeanId != null) { bean = beanContext.getBean(wireBeanId); } if(bean != null) { if(!BeanWiringObserver.isMatchingBean(bean, wireBeanType, wireBeanAnnotation)) { bean = null; } } if(bean == null) { if(logger.isDebugEnabled()) { logger.debug("Registering bean ADD wiring observer for wiring bean '" + wireBeanId + "' onto target bean '" + beanId.getName() + "'."); } // Register the observer which looks for the creation of the selected bean via its beanIdName... beanContext.addObserver(wireByBeanIdObserver); } else { populateAndSetPropertyValue(bean, beanContext, wireBeanId, executionContext, source); } } public void populateAndSetPropertyValue(Object bean, BeanContext beanContext, BeanId targetBeanId, final ExecutionContext executionContext, Fragment source) { BeanRuntimeInfo wiredBeanRI = getWiredBeanRuntimeInfo(); // When this observer is triggered then we look if we got something we can set immediately or that we got an array collection. // For an array collection, we need the array representation and not the list representation, so we register and observer that // listens for the change from the list to the array... if(wiredBeanRI != null && wiredBeanRI.getClassification() == Classification.ARRAY_COLLECTION ) { if(logger.isDebugEnabled()) { logger.debug("Registering bean CHANGE wiring observer for wiring bean '" + targetBeanId + "' onto target bean '" + beanId.getName() + "' after it has been converted from a List to an array."); } // Register an observer which looks for the change that the mutable list of the selected bean gets converted to an array. We // can then set this array beanContext.addObserver(listToArrayChangeObserver); } else { setPropertyValue(property, bean, executionContext, source); } } private void bindExpressionValue(String mapPropertyName, String dataString, ExecutionContext executionContext, Fragment source) { Map beanMap = executionContext.getBeanContext().getBeanMap(); Map variables = new HashMap(); if(expressionHasDataVariable) { variables.put(EXPRESSION_VALUE_VARIABLE_NAME, dataString); } Object dataObject = expressionEvaluator.exec(beanMap, variables); decodeAndSetPropertyValue(mapPropertyName, dataObject, executionContext, source); } private void decodeAndSetPropertyValue(String mapPropertyName, Object dataObject, ExecutionContext executionContext, Fragment source) { if(dataObject instanceof String) { setPropertyValue(mapPropertyName, decodeDataString((String) dataObject, executionContext), executionContext, source); } else { setPropertyValue(mapPropertyName, dataObject, executionContext, source); } } @SuppressWarnings("unchecked") public void setPropertyValue(String mapPropertyName, Object dataObject, ExecutionContext executionContext, Fragment source) { if ( dataObject == null ) { return; } Object bean = executionContext.getBeanContext().getBean(beanId); Classification beanType = beanRuntimeInfo.getClassification(); createPropertySetterMethod(bean, dataObject.getClass()); if(logger.isDebugEnabled()) { logger.debug("Setting data object '" + wireBeanIdName + "' (" + dataObject.getClass().getName() + ") on target bean '" + beanId + "'."); } // Set the data on the bean... try { if(propertySetterMethod != null) { propertySetterMethod.invoke(bean, dataObject); } else if(beanType == Classification.MAP_COLLECTION) { ((Map)bean).put(mapPropertyName, dataObject); } else if(beanType == Classification.ARRAY_COLLECTION || beanType == Classification.COLLECTION_COLLECTION) { ((Collection)bean).add(dataObject); } else if(propertySetterMethod == null) { if(setterMethod != null) { throw new SmooksConfigurationException("Bean [" + beanIdName + "] configuration invalid. Bean setter method [" + setterMethod + "(" + dataObject.getClass().getName() + ")] not found on type [" + beanRuntimeInfo.getPopulateType().getName() + "]. You may need to set a 'decoder' on the binding config."); } else if(property != null) { throw new SmooksConfigurationException("Bean [" + beanIdName + "] configuration invalid. Bean setter method [" + ClassUtil.toSetterName(property) + "(" + dataObject.getClass().getName() + ")] not found on type [" + beanRuntimeInfo.getPopulateType().getName() + "]. You may need to set a 'decoder' on the binding config."); } } if(notifyPopulate) { BeanContextLifecycleEvent event = new BeanContextLifecycleEvent(executionContext, source, BeanLifecycle.POPULATE, beanId, bean); executionContext.getBeanContext().notifyObservers(event); } } catch (IllegalAccessException e) { throw new SmooksConfigurationException("Error invoking bean setter method [" + ClassUtil.toSetterName(property) + "] on bean instance class type [" + bean.getClass() + "].", e); } catch (InvocationTargetException e) { throw new SmooksConfigurationException("Error invoking bean setter method [" + ClassUtil.toSetterName(property) + "] on bean instance class type [" + bean.getClass() + "].", e); } } private void createPropertySetterMethod(Object bean, Class parameter) { if (!checkedForSetterMethod && propertySetterMethod == null) { String methodName = null; if(setterMethod != null && !setterMethod.trim().equals("")) { methodName = setterMethod; } else if(property != null && !property.trim().equals("")) { methodName = ClassUtil.toSetterName(property); } if(methodName != null) { propertySetterMethod = createPropertySetterMethod(bean, methodName, parameter); } checkedForSetterMethod = true; } } /** * Create the bean setter method instance for this visitor. * * @param setterName The setter method name. * @return The bean setter method. */ private synchronized Method createPropertySetterMethod(Object bean, String setterName, Class setterParamType) { if (propertySetterMethod == null) { propertySetterMethod = BeanUtils.createSetterMethod(setterName, bean, setterParamType); } return propertySetterMethod; } private Object decodeDataString(String dataString, ExecutionContext executionContext) throws DataDecodeException { if((dataString == null || dataString.length() == 0) && defaultVal != null) { if(defaultVal.equals("null")) { return null; } dataString = defaultVal; } if (decoder == null) { decoder = getDecoder(executionContext); } try { return decoder.decode(dataString); } catch(DataDecodeException e) { throw new DataDecodeException("Failed to decode binding value '" + dataString + "' for property '" + property + "' on bean '" + beanId.getName() +"'.", e); } } private DataDecoder getDecoder(ExecutionContext executionContext) throws DataDecodeException { @SuppressWarnings("unchecked") List decoders = executionContext.getDeliveryConfig().getObjects("decoder:" + typeAlias); if (decoders == null || decoders.isEmpty()) { if(typeAlias != null) { decoder = DataDecoder.Factory.create(typeAlias); } else { decoder = resolveDecoderReflectively(); } } else if (!(decoders.get(0) instanceof DataDecoder)) { throw new DataDecodeException("Configured decoder '" + typeAlias + ":" + decoders.get(0).getClass().getName() + "' is not an instance of " + DataDecoder.class.getName()); } else { decoder = (DataDecoder) decoders.get(0); } if(decoder instanceof PreprocessDecoder) { PreprocessDecoder preprocessDecoder = (PreprocessDecoder) decoder; if(preprocessDecoder.getBaseDecoder() == null) { preprocessDecoder.setBaseDecoder(resolveDecoderReflectively()); } } return decoder; } private DataDecoder resolveDecoderReflectively() throws DataDecodeException { Class bindType = resolveBindTypeReflectively(); if(bindType != null) { DataDecoder resolvedDecoder = DataDecoder.Factory.create(bindType); if(resolvedDecoder != null) { return resolvedDecoder; } } return new StringDecoder(); } private Class resolveBindTypeReflectively() throws DataDecodeException { String bindingMember = (setterMethod != null? setterMethod : property); if(bindingMember != null && beanRuntimeInfo.getClassification() == Classification.NON_COLLECTION) { Method bindingMethod = Bean.getBindingMethod(bindingMember, beanRuntimeInfo.getPopulateType()); if(bindingMethod != null) { return bindingMethod.getParameterTypes()[0]; } } return null; } private BeanRuntimeInfo getWiredBeanRuntimeInfo() { if(wiredBeanRuntimeInfo == null) { // Don't need to synchronize this. Worse thing that can happen is we initialize it // more than once... no biggie... wiredBeanRuntimeInfo = BeanRuntimeInfo.getBeanRuntimeInfo(wireBeanIdName, appContext); } return wiredBeanRuntimeInfo; } private String getId() { return id; } public Set getProducts() { return CollectionsUtil.toSet(beanIdName + "." + property, "]." + property); } public boolean consumes(Object object) { if(object.equals(beanIdName)) { return true; } else if(wireBeanIdName != null && object.equals(wireBeanIdName)) { return true; } else if(expressionEvaluator != null && expressionEvaluator.getExpression().indexOf(object.toString()) != -1) { return true; } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy