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

org.smooks.cartridges.javabean.Bean Maven / Gradle / Ivy

The newest version!
/*-
 * ========================LICENSE_START=================================
 * smooks-javabean-cartridge
 * %%
 * Copyright (C) 2020 Smooks
 * %%
 * Licensed under the terms of the Apache License Version 2.0, or
 * the GNU Lesser General Public License version 3.0 or later.
 *
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-or-later
 *
 * ======================================================================
 *
 * 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.
 *
 * ======================================================================
 *
 * This program 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 3 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * =========================LICENSE_END==================================
 */
package org.smooks.cartridges.javabean;

import org.smooks.Smooks;
import org.smooks.api.Registry;
import org.smooks.api.converter.TypeConverter;
import org.smooks.api.converter.TypeConverterFactory;
import org.smooks.api.delivery.ContentHandlerBinding;
import org.smooks.api.resource.config.ResourceConfig;
import org.smooks.api.resource.visitor.Visitor;
import org.smooks.assertion.AssertArgument;
import org.smooks.cartridges.javabean.ext.SelectorPropertyResolver;
import org.smooks.cartridges.javabean.factory.Factory;
import org.smooks.engine.converter.TypeConverterFactoryLoader;
import org.smooks.engine.delivery.DefaultContentHandlerBinding;
import org.smooks.engine.lookup.NamespaceManagerLookup;
import org.smooks.engine.lookup.converter.SourceTargetTypeConverterFactoryLookup;
import org.smooks.engine.resource.config.DefaultResourceConfig;
import org.smooks.support.ClassUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * Programmatic Bean Configurator.
 * 

* This class can be used to programmatically configure a Smooks instance for performing * a Java Bindings on a specific class. To populate a graph, you simply create a graph of * Bean instances by binding Beans onto Beans. *

* This class uses a Fluent API (all methods return the Bean instance), making it easy to * string configurations together to build up a graph of Bean configuration. *

*

Example

* Taking the "classic" Order message as an example and binding it into a corresponding * Java Object model. *

The Message

*
 * <order xmlns="http://x">
 *     <header>
 *         <y:date xmlns:y="http://y">Wed Nov 15 13:45:28 EST 2006</y:date>
 *         <customer number="123123">Joe</customer>
 *         <privatePerson></privatePerson>
 *     </header>
 *     <order-items>
 *         <order-item>
 *             <product>111</product>
 *             <quantity>2</quantity>
 *             <price>8.90</price>
 *         </order-item>
 *         <order-item>
 *             <product>222</product>
 *             <quantity>7</quantity>
 *             <price>5.20</price>
 *         </order-item>
 *     </order-items>
 * </order>
 * 
*

*

The Java Model

* (Not including getters and setters): *
 * public class Order {
 *     private Header header;
 *     private List<OrderItem> orderItems;
 * }
 * public class Header {
 *     private Long customerNumber;
 *     private String customerName;
 * }
 * public class OrderItem {
 *     private long productId;
 *     private Integer quantity;
 *     private double price;
 * }
 * 
*

*

The Binding Configuration and Execution Code

* The configuration code (Note: Smooks instance defined and instantiated globally): *
 * Smooks smooks = new Smooks();
 *
 * Bean orderBean = new Bean(Order.class, "order", "/order");
 * orderBean.bindTo("header",
 *     orderBean.newBean(Header.class, "/order")
 *         .bindTo("customerNumber", "header/customer/@number")
 *         .bindTo("customerName", "header/customer")
 *     ).bindTo("orderItems",
 *     orderBean.newBean(ArrayList.class, "/order")
 *         .bindTo(orderBean.newBean(OrderItem.class, "order-item")
 *             .bindTo("productId", "order-item/product")
 *             .bindTo("quantity", "order-item/quantity")
 *             .bindTo("price", "order-item/price"))
 *     );
 *
 * smooks.addVisitors(orderBean);
 * 
*

* And the execution code: *

 * JavaResult result = new JavaResult();
 *
 * smooks.filterSource(new StreamSource(orderMessageStream), result);
 * Order order = (Order) result.getBean("order");
 * 
* * @author [email protected] * @see Value */ public class Bean extends BindingAppender { protected static volatile Set> TYPE_CONVERTER_FACTORIES = null; protected final Registry registry; protected final BeanInstanceCreator beanInstanceCreator; protected final Class beanClass; protected final String createOnElement; protected final List bindings = new ArrayList<>(); protected final List wirings = new ArrayList<>(); protected boolean processed = false; /** * Create a Bean binding configuration. *

* The bean instance is created on the root/document fragment. * * @param beanClass The bean runtime class. * @param beanId The bean ID. */ @SuppressWarnings("unchecked") public Bean(Class beanClass, String beanId, Registry registry) { this(beanClass, beanId, (Factory) null, registry); } /** * Create a Bean binding configuration. *

* The bean instance is created on the root/document fragment. * * @param beanClass The bean runtime class. * @param beanId The bean ID. * @param factory The factory that will create the runtime object */ public Bean(Class beanClass, String beanId, Factory factory, Registry registry) { this(beanClass, beanId, ResourceConfig.DOCUMENT_FRAGMENT_SELECTOR, factory, registry); } /** * Create a Bean binding configuration. * * @param beanClass The bean runtime class. * @param beanId The bean ID. * @param createOnElement The element selector used to create the bean instance. */ public Bean(Class beanClass, String beanId, String createOnElement, Registry registry) { this(beanClass, beanId, createOnElement, null, registry); } /** * Create a Bean binding configuration. * * @param beanClass The bean runtime class. * @param beanId The bean ID. * @param createOnElement The element selector used to create the bean instance. * @param factory The factory that will create the runtime object */ public Bean(Class beanClass, String beanId, String createOnElement, Factory factory, Registry registry) { super(beanId); AssertArgument.isNotNull(beanClass, "beanClass"); AssertArgument.isNotNull(createOnElement, "createOnElement"); if (TYPE_CONVERTER_FACTORIES == null) { synchronized (Bean.class) { if (TYPE_CONVERTER_FACTORIES == null) { TYPE_CONVERTER_FACTORIES = new TypeConverterFactoryLoader().load(registry.getClassLoader()); } } } this.beanClass = beanClass; this.createOnElement = createOnElement; this.registry = registry; beanInstanceCreator = new BeanInstanceCreator(beanId, beanClass, factory); } /** * Create a Bean binding configuration. * * @param beanClass The bean runtime class. * @param beanId The bean ID. * @param createOnElement The element selector used to create the bean instance. */ public static Bean newBean(Class beanClass, String beanId, String createOnElement, Registry registry) { return new Bean(beanClass, beanId, createOnElement, registry); } /** * Create a Bean binding configuration. * * @param beanClass The bean runtime class. * @param beanId The bean ID. * @param createOnElement The element selector used to create the bean instance. * @param factory The factory that will create the runtime object */ public static Bean newBean(Class beanClass, String beanId, String createOnElement, Factory factory, Registry registry) { return new Bean(beanClass, beanId, createOnElement, factory, registry); } /** * Create a Bean binding configuration. *

* This method binds the configuration to the same {@link Smooks} instance * supplied in the constructor. The beanId is generated. * * @param beanClass The bean runtime class. * @param createOnElement The element selector used to create the bean instance. * @return this Bean configuration instance. */ public Bean newBean(Class beanClass, String createOnElement) { String randomBeanId = UUID.randomUUID().toString(); return new Bean(beanClass, randomBeanId, createOnElement, registry); } /** * Create a Bean binding configuration. *

* This method binds the configuration to the same {@link Smooks} instance * supplied in the constructor. The beanId is generated. * * @param beanClass The bean runtime class. * @param createOnElement The element selector used to create the bean instance. * @param factory The factory that will create the runtime object * @return this Bean configuration instance. */ public Bean newBean(Class beanClass, String createOnElement, Factory factory) { return new Bean(beanClass, UUID.randomUUID().toString(), createOnElement, factory, registry); } /** * Create a Bean binding configuration. *

* This method binds the configuration to the same {@link Smooks} instance * supplied in the constructor. * * @param beanClass The bean runtime class. * @param beanId The beanId. * @param createOnElement The element selector used to create the bean instance. * @return this Bean configuration instance. */ public Bean newBean(Class beanClass, String beanId, String createOnElement) { return new Bean(beanClass, beanId, createOnElement, registry); } /** * Create a Bean binding configuration. *

* This method binds the configuration to the same {@link Smooks} instance * supplied in the constructor. * * @param beanClass The bean runtime class. * @param beanId The beanId. * @param createOnElement The element selector used to create the bean instance. * @param factory The factory that will create the runtime object * @return this Bean configuration instance. */ public Bean newBean(Class beanClass, String beanId, String createOnElement, Factory factory) { return new Bean(beanClass, beanId, createOnElement, factory, registry); } /** * Create a binding configuration to bind the data, selected from the message by the * dataSelector, to the specified bindingMember (field/method). *

* Discovers the {@link org.smooks.javabean.DataDecoder} through the specified * bindingMember. * * @param bindingMember The name of the binding member. This is a bean property (field) * or method name. * @param dataSelector The data selector for the data value to be bound. * @return The Bean configuration instance. */ public Bean bindTo(String bindingMember, String dataSelector) { return bindTo(bindingMember, dataSelector, null); } /** * Create a binding configuration to bind the data, selected from the message by the * dataSelector, to the target Bean member specified by the bindingMember param. * * @param bindingMember The name of the binding member. This is a bean property (field) * or method name. * @param dataSelector The data selector for the data value to be bound. * @param dataDecoder The {@link org.smooks.javabean.DataDecoder} to be used for decoding * the data value. * @return this Bean configuration instance. */ public Bean bindTo(String bindingMember, String dataSelector, TypeConverterFactory typeConverterFactory) { assertNotProcessed(); AssertArgument.isNotNull(bindingMember, "bindingMember"); AssertArgument.isNotNull(dataSelector, "dataSelector"); // dataDecoder can be null BeanInstancePopulator beanInstancePopulator = new BeanInstancePopulator(); ResourceConfig populatorResourceConfig = new DefaultResourceConfig(dataSelector, registry.lookup(new NamespaceManagerLookup()).orElse(new Properties())); SelectorPropertyResolver.resolveSelectorTokens(populatorResourceConfig); // Configure the populator visitor... beanInstancePopulator.setBeanId(getBeanId()); beanInstancePopulator.setValueAttributeName(populatorResourceConfig.getParameterValue(BeanInstancePopulator.VALUE_ATTRIBUTE_NAME, String.class)); beanInstancePopulator.setValueAttributePrefix(populatorResourceConfig.getParameterValue(BeanInstancePopulator.VALUE_ATTRIBUTE_PREFIX, String.class)); Method bindingMethod = getBindingMethod(bindingMember, beanClass); if (bindingMethod != null) { if (typeConverterFactory == null) { final Class dataType = bindingMethod.getParameterTypes()[0]; final TypeConverterFactory stringTypeConverterFactory = new SourceTargetTypeConverterFactoryLookup<>(String.class, dataType).lookup(TYPE_CONVERTER_FACTORIES); if (stringTypeConverterFactory != null) { typeConverterFactory = stringTypeConverterFactory; } else { typeConverterFactory = new SourceTargetTypeConverterFactoryLookup<>(Object.class, Object.class).lookup(TYPE_CONVERTER_FACTORIES); } } if (bindingMethod.getName().equals(bindingMember)) { beanInstancePopulator.setSetterMethod(bindingMethod.getName()); } else { beanInstancePopulator.setProperty(bindingMember); } } else { beanInstancePopulator.setProperty(bindingMember); } beanInstancePopulator.setTypeConverterFactory(typeConverterFactory); bindings.add(new Binding(populatorResourceConfig.getSelectorPath().getSelector(), beanInstancePopulator, false)); return this; } /** * Add a bean binding configuration for the specified bindingMember (field/method) to * this bean binding config. *

* This method is used to build a binding configuration graph, which in turn configures * Smooks to build a Java Object Graph (ala <jb:wiring> configurations). * * @param bindingMember The name of the binding member. This is a bean property (field) * or method name. The bean runtime class should match the * @param bean The Bean instance to be bound * @return this Bean configuration instance. */ public Bean bindTo(String bindingMember, Bean bean) { assertNotProcessed(); AssertArgument.isNotNull(bindingMember, "bindingMember"); AssertArgument.isNotNull(bean, "bean"); BeanInstancePopulator beanInstancePopulator = new BeanInstancePopulator(); // Configure the populator visitor... beanInstancePopulator.setBeanId(getBeanId()); beanInstancePopulator.setWireBeanId(bean.getBeanId()); Method bindingMethod = getBindingMethod(bindingMember, beanClass); if (bindingMethod != null) { if (bindingMethod.getName().equals(bindingMember)) { beanInstancePopulator.setSetterMethod(bindingMethod.getName()); } else { beanInstancePopulator.setProperty(bindingMember); } } else { beanInstancePopulator.setProperty(bindingMember); } bindings.add(new Binding(createOnElement, beanInstancePopulator, false)); wirings.add(bean); return this; } /** * Add a bean binding configuration to this Collection/array bean binding config. *

* This method checks that this bean's beanClass is a Collection/array, generating an * {@link IllegalArgumentException} if the check fails. * * @param bean The Bean instance to be bound * @return this Bean configuration instance. * @throws IllegalArgumentException this Bean's beanClass (not the supplied bean!) is * not a Collection/array. You cannot call this method on Bean configurations whose beanClass is not a * Collection/array. For non Collection/array types, you must use one of the bindTo meths that specify a * 'bindingMember'. */ public Bean bindTo(Bean bean) throws IllegalArgumentException { assertNotProcessed(); AssertArgument.isNotNull(bean, "bean"); BeanInstancePopulator beanInstancePopulator = new BeanInstancePopulator(); // Configure the populator visitor... beanInstancePopulator.setBeanId(getBeanId()); beanInstancePopulator.setWireBeanId(bean.getBeanId()); bindings.add(new Binding(createOnElement, beanInstancePopulator, true)); wirings.add(bean); return this; } /** * Create a binding configuration to bind the data, selected from the message by the * dataSelector, to the target Collection/array Bean beanclass instance. * * @param dataSelector The data selector for the data value to be bound. * @return this Bean configuration instance. */ public Bean bindTo(String dataSelector) { return bindTo(dataSelector, (TypeConverterFactory) null); } /** * Create a binding configuration to bind the data, selected from the message by the * dataSelector, to the target Collection/array Bean beanclass instance. * * @param dataSelector The data selector for the data value to be bound. * @param typeConverter The {@link org.smooks.javabean.DataDecoder} to be used for decoding * the data value. * @return this Bean configuration instance. */ public Bean bindTo(String dataSelector, TypeConverterFactory typeConverterFactory) { assertNotProcessed(); AssertArgument.isNotNull(dataSelector, "dataSelector"); // dataDecoder can be null BeanInstancePopulator beanInstancePopulator = new BeanInstancePopulator(); ResourceConfig populatorResourceConfig = new DefaultResourceConfig(dataSelector, new Properties()); SelectorPropertyResolver.resolveSelectorTokens(populatorResourceConfig); // Configure the populator visitor... beanInstancePopulator.setBeanId(getBeanId()); beanInstancePopulator.setValueAttributeName(populatorResourceConfig.getParameterValue(BeanInstancePopulator.VALUE_ATTRIBUTE_NAME, String.class)); beanInstancePopulator.setValueAttributePrefix(populatorResourceConfig.getParameterValue(BeanInstancePopulator.VALUE_ATTRIBUTE_PREFIX, String.class)); beanInstancePopulator.setTypeConverterFactory(typeConverterFactory); bindings.add(new Binding(populatorResourceConfig.getSelectorPath().getSelector(), beanInstancePopulator, true)); return this; } /** * Add the visitors, associated with this Bean instance, to the visitor map. * * @param visitorMap The visitor Map. */ @Override public List> addVisitors() { // Need to protect against multiple calls. This can happen where e.g. beans are // wired together in 2-way relationships, or the creating code doesn't use the // fluent interface and calls Smooks.addVisitor to each bean instance. if (processed) { return Collections.EMPTY_LIST; } processed = true; List> visitorBindings = new ArrayList<>(); // Add the create bean visitor... ContentHandlerBinding beanInstanceCreateBinding = new DefaultContentHandlerBinding<>(beanInstanceCreator, createOnElement, registry); ResourceConfig beanInstanceCreatorSmooksResourceConfiguration = beanInstanceCreateBinding.getResourceConfig(); beanInstanceCreatorSmooksResourceConfiguration.setParameter("beanId", getBeanId()); beanInstanceCreatorSmooksResourceConfiguration.setParameter("beanClass", beanClass.getName()); visitorBindings.add(beanInstanceCreateBinding); // Recurse down the wired beans... for (Bean bean : wirings) { visitorBindings.addAll(bean.addVisitors()); } // Add the populate bean visitors... for (Binding binding : bindings) { ContentHandlerBinding beanInstancePopulatorBinding = new DefaultContentHandlerBinding<>(binding.beanInstancePopulator, binding.selector, registry); beanInstancePopulatorBinding.getResourceConfig().setParameter("beanId", getBeanId()); visitorBindings.add(beanInstancePopulatorBinding); if (binding.assertTargetIsCollection) { assertBeanClassIsCollection(); } } return visitorBindings; } /** * Get the bean binding class Member (field/method). * * @param bindingMember Binding member name. * @return The binding member, or null if not found. */ public static Method getBindingMethod(String bindingMember, Class beanClass) { Method[] methods = beanClass.getMethods(); // Check is the bindingMember an actual fully qualified method name... for (Method method : methods) { if (method.getName().equals(bindingMember) && method.getParameterTypes().length == 1) { return method; } } // Check is the bindingMember defined by a property name. If so, there should be a // bean setter method for that property... String asPropertySetterMethod = ClassUtils.toSetterName(bindingMember); for (Method method : methods) { if (method.getName().equals(asPropertySetterMethod) && method.getParameterTypes().length == 1) { return method; } } // Can't resolve it... return null; } /** * Assert that the beanClass associated with this configuration is an array or Collection. */ protected void assertBeanClassIsCollection() { BeanRuntimeInfo beanRuntimeInfo = beanInstanceCreator.getBeanRuntimeInfo(); if (beanRuntimeInfo.getClassification() != BeanRuntimeInfo.Classification.COLLECTION_COLLECTION && beanRuntimeInfo.getClassification() != BeanRuntimeInfo.Classification.ARRAY_COLLECTION) { throw new IllegalArgumentException("Invalid call to a Collection/array Bean.bindTo method for a non Collection/Array target. Binding target type '" + beanRuntimeInfo.getPopulateType().getName() + "' (beanId '" + getBeanId() + "'). Use one of the Bean.bindTo methods that specify a 'bindingMember' argument."); } } protected void assertNotProcessed() { if (processed) { throw new IllegalStateException("Unexpected attempt to bindTo Bean instance after the Bean instance has been added to a Smooks instance."); } } protected static class Binding { protected final String selector; protected final BeanInstancePopulator beanInstancePopulator; protected final boolean assertTargetIsCollection; protected Binding(String selector, BeanInstancePopulator beanInstancePopulator, boolean assertTargetIsCollection) { this.selector = selector; this.beanInstancePopulator = beanInstancePopulator; this.assertTargetIsCollection = assertTargetIsCollection; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy