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

org.smooks.cartridges.javabean.binding.xml.XMLBinding 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.binding.xml;

import org.smooks.Smooks;
import org.smooks.api.SmooksConfigException;
import org.smooks.api.converter.TypeConverter;
import org.smooks.api.converter.TypeConverterFactory;
import org.smooks.api.io.Source;
import org.smooks.api.resource.config.Configurable;
import org.smooks.api.resource.config.ResourceConfig;
import org.smooks.api.resource.config.ResourceConfigSeq;
import org.smooks.api.resource.config.xpath.SelectorPath;
import org.smooks.api.resource.config.xpath.SelectorStep;
import org.smooks.assertion.AssertArgument;
import org.smooks.cartridges.javabean.BeanInstanceCreator;
import org.smooks.cartridges.javabean.BeanInstancePopulator;
import org.smooks.cartridges.javabean.binding.AbstractBinding;
import org.smooks.cartridges.javabean.binding.BeanSerializationException;
import org.smooks.cartridges.javabean.binding.SerializationContext;
import org.smooks.cartridges.javabean.binding.model.Bean;
import org.smooks.cartridges.javabean.binding.model.Binding;
import org.smooks.cartridges.javabean.binding.model.DataBinding;
import org.smooks.cartridges.javabean.binding.model.ModelSet;
import org.smooks.cartridges.javabean.binding.model.WiredBinding;
import org.smooks.cartridges.javabean.binding.model.get.ConstantGetter;
import org.smooks.cartridges.javabean.binding.model.get.GetterGraph;
import org.smooks.engine.lookup.ContentHandlerFactoryLookup;
import org.smooks.engine.lookup.NamespaceManagerLookup;
import org.smooks.engine.lookup.converter.SourceTargetTypeConverterFactoryLookup;
import org.smooks.engine.resource.config.xpath.step.AttributeSelectorStep;
import org.smooks.engine.resource.config.xpath.step.ElementSelectorStep;
import org.smooks.io.source.StringSource;
import org.smooks.support.ClassUtils;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * XML Binding class.
 * 

* This class is designed specifically for reading and writing XML data (does not work for other data formats) * to and from Java Object models using nothing more than standard <jb:bean> configurations i.e. * no need to write a template for serializing the Java Objects to an output character based format, * as with Smooks v1.4 and before. * * @author [email protected] * @since 1.5 */ @SuppressWarnings("unchecked") public class XMLBinding extends AbstractBinding { protected ModelSet beanModelSet; protected List graphs; protected final Set rootElementNames = new HashSet<>(); protected final Map serializers = new LinkedHashMap<>(); protected boolean omitXMLDeclaration = false; /** * Public constructor. *

* Must be followed by calls to the {@link #add(java.io.InputStream)} (or {@link #add(String)}) method * and then the {@link #initialise()} method. */ public XMLBinding() { super(); } /** * Public constructor. *

* Create an instance using a pre-configured Smooks instance. * * @param smooks The pre-configured Smooks instance. */ public XMLBinding(Smooks smooks) { super(smooks); } @Override public XMLBinding add(String smooksConfigURI) throws IOException, SAXException { super.add(smooksConfigURI); return this; } @Override public XMLBinding add(InputStream smooksConfigStream) throws IOException, SAXException { return (XMLBinding) super.add(smooksConfigStream); } @Override public XMLBinding setReportPath(String reportPath) { super.setReportPath(reportPath); return this; } @Override public XMLBinding initialise() { super.initialise(); beanModelSet = ModelSet.get(getSmooks().getApplicationContext()); graphs = createExpandedXMLOutputGraphs(getUserDefinedResourceList()); createRootSerializers(graphs); mergeBeanModelsIntoXMLGraphs(); return this; } /** * Turn on/off outputting of the XML declaration when executing the {@link #toXML(Object, java.io.Writer)} method. * * @param omitXMLDeclaration True if the order is to be omitted, otherwise false. * @return this instance. */ public XMLBinding setOmitXMLDeclaration(boolean omitXMLDeclaration) { this.omitXMLDeclaration = omitXMLDeclaration; return this; } /** * Bind from the XML into the Java Object model. * * @param inputSource The XML input. * @param toType The Java type to which the XML data is to be bound. * @param The Java type to which the XML data is to be bound. * @return The populated Java instance. */ public T fromXML(String inputSource, Class toType) { try { return bind(new StringSource(inputSource), toType); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException from a String input.", e); } } /** * Bind from the XML into the Java Object model. * * @param inputSource The XML input. * @param toType The Java type to which the XML data is to be bound. * @param The Java type to which the XML data is to be bound. * @return The populated Java instance. */ public T fromXML(Source inputSource, Class toType) throws IOException { return bind(inputSource, toType); } /** * Write the supplied Object instance to XML. * * @param object The Object instance. * @param outputWriter The output writer. * @param The Writer type. * @return The supplied {@link Writer} instance}. * @throws BeanSerializationException Error serializing the bean. * @throws IOException Error writing to the supplied Writer instance. */ public W toXML(Object object, W outputWriter) throws BeanSerializationException, IOException { AssertArgument.isNotNull(object, "object"); assertInitialized(); Class objectClass = object.getClass(); RootNodeSerializer rootNodeSerializer = serializers.get(objectClass); if (rootNodeSerializer == null) { throw new BeanSerializationException("No serializer for Java type '" + objectClass.getName() + "'."); } if (!omitXMLDeclaration) { outputWriter.write("\n"); } XMLElementSerializationNode serializer = rootNodeSerializer.serializer; serializer.serialize(outputWriter, new SerializationContext(object, rootNodeSerializer.beanId)); outputWriter.flush(); return outputWriter; } /** * Write the supplied Object instance to XML. *

* This is a simple wrapper on the {@link #toXML(Object, java.io.Writer)} method. * * @param object The Object instance. * @return The XML as a String. * @throws BeanSerializationException Error serializing the bean. */ public String toXML(Object object) throws BeanSerializationException { StringWriter writer = new StringWriter(); try { toXML(object, writer); return writer.toString(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException writing to a StringWriter.", e); } finally { try { writer.close(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException closing a StringWriter.", e); } } } protected void mergeBeanModelsIntoXMLGraphs() { Set> serializerSet = serializers.entrySet(); for (Map.Entry rootNodeSerializer : serializerSet) { Bean model = beanModelSet.getModel(rootNodeSerializer.getKey()); if (model == null) { throw new IllegalStateException("Unexpected error. No Bean model for type '" + rootNodeSerializer.getKey().getName() + "'."); } merge(rootNodeSerializer.getValue().serializer, model); } } protected void merge(XMLElementSerializationNode serializer, Bean bean) { boolean isCollection = bean.isCollection(); for (Binding binding : bean.getBindings()) { BeanInstancePopulator populator = binding.getPopulator(); if (!isCollection && binding instanceof DataBinding) { XMLSerializationNode node = serializer.findNode(populator.getConfig().getSelectorPath()); if (node != null) { node.setGetter(constructContextualGetter((DataBinding) binding)); Method getterMethodByProperty = ClassUtils.getGetterMethodByProperty(binding.getProperty(), bean.getBeanClass(), null); TypeConverterFactory beanPopulatorTypeConverterFactory = binding.getPopulator().getTypeConverterFactory(getSmooks().createExecutionContext().getContentDeliveryRuntime().getContentDeliveryConfig()); TypeConverter beanPopulatorTypeConverter = beanPopulatorTypeConverterFactory.createTypeConverter(); TypeConverterFactory xmlBindingTypeFactory = getSmooks().getApplicationContext().getRegistry().lookup(new SourceTargetTypeConverterFactoryLookup<>(getterMethodByProperty.getReturnType(), String.class)); if (xmlBindingTypeFactory != null) { TypeConverter xmlBindingTypeConverter = xmlBindingTypeFactory.createTypeConverter(); if (xmlBindingTypeConverter instanceof Configurable && beanPopulatorTypeConverter instanceof Configurable) { ((Configurable) xmlBindingTypeConverter).setConfiguration(((Configurable) beanPopulatorTypeConverter).getConfiguration()); } node.setTypeConverter(xmlBindingTypeConverter); } } } else if (binding instanceof WiredBinding) { Bean wiredBean = ((WiredBinding) binding).getWiredBean(); XMLElementSerializationNode node = (XMLElementSerializationNode) serializer.findNode(wiredBean.getCreator().getConfig().getSelectorPath()); if (node != null) { if (isCollection) { // Mark the node that creates the wiredBean as being a collection item node... Bean collectionBean = wiredBean.getWiredInto(); GetterGraph getter = constructContextualGetter(collectionBean); node.setIsCollection(true); node.setCollectionGetter(wiredBean.getBeanId(), getter); } else { node.setGetter(constructContextualGetter(wiredBean)); } } merge(serializer, wiredBean); } } } protected void createRootSerializers(List graphs) { Collection beanModels = beanModelSet.getModels().values(); for (Bean model : beanModels) { BeanInstanceCreator creator = model.getCreator(); SelectorPath selectorPath = creator.getConfig().getSelectorPath(); XMLElementSerializationNode createNode = (XMLElementSerializationNode) findNode(graphs, selectorPath); // Only create serializers for routed elements... if (rootElementNames.contains(createNode.getQName())) { createNode = ((XMLElementSerializationNode) createNode.clone()); createNode.setParent(null); Class beanClass = creator.getBeanRuntimeInfo().getPopulateType(); if (!Collection.class.isAssignableFrom(beanClass)) { // Ignore Collections... don't allow them to be serialized.... not enough type info. serializers.put(beanClass, new RootNodeSerializer(creator.getBeanId(), createNode)); addNamespaceAttributes(createNode); } } } } protected void addNamespaceAttributes(XMLElementSerializationNode serializer) { Properties namespaces = getSmooks().getApplicationContext().getRegistry().lookup(new NamespaceManagerLookup()).orElse(null); if (namespaces != null) { Enumeration namespacePrefixes = (Enumeration) namespaces.propertyNames(); while (namespacePrefixes.hasMoreElements()) { String prefix = namespacePrefixes.nextElement(); String namespace = namespaces.getProperty(prefix); QName nsAttributeName = new QName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, XMLConstants.XMLNS_ATTRIBUTE); XMLAttributeSerializationNode nsAttribute = new XMLAttributeSerializationNode(nsAttributeName); serializer.getAttributes().add(nsAttribute); nsAttribute.setGetter(new ConstantGetter(namespace)); } } } protected List createExpandedXMLOutputGraphs(final ResourceConfigSeq resourceConfigSeq) { final List graphRoots = new ArrayList<>(); for (int i = 0; i < resourceConfigSeq.size(); i++) { final ResourceConfig resourceConfig = resourceConfigSeq.get(i); final Object javaResource; if (resourceConfig.isJavaResource()) { javaResource = getSmooks().getApplicationContext().getRegistry().lookup(new ContentHandlerFactoryLookup("class")).create(resourceConfig); } else { javaResource = null; } if (javaResource instanceof BeanInstanceCreator) { assertSelectorOK(resourceConfig); constructNodePath(resourceConfig.getSelectorPath(), graphRoots); } else if (javaResource instanceof BeanInstancePopulator) { assertSelectorOK(resourceConfig); constructNodePath(resourceConfig.getSelectorPath(), graphRoots); } } return graphRoots; } protected XMLSerializationNode constructNodePath(SelectorPath selectorPath, List graphRoots) { if (selectorPath == null || selectorPath.size() == 0) { throw new IllegalStateException("Invalid binding configuration. All configuration elements must specify fully qualified selector paths (createOnElement, data, executeOnElement attributes etc.)."); } SelectorStep rootSelectorStep = selectorPath.get(1); XMLElementSerializationNode root = XMLElementSerializationNode.getElement(rootSelectorStep, graphRoots, true); if (selectorPath.size() == 3 && selectorPath.get(2) instanceof AttributeSelectorStep) { // It's an attribute node... return XMLElementSerializationNode.addAttributeNode(root, selectorPath.get(2), true); } else if (selectorPath.size() > 2) { return root.getPathNode(selectorPath, 2, true); } else { return root; } } protected XMLSerializationNode findNode(List graphs, SelectorPath selectorPath) { XMLElementSerializationNode root = XMLElementSerializationNode.getElement(selectorPath.get(1), graphs, false); XMLSerializationNode node = root; if (selectorPath.size() > 2) { node = root.getPathNode(selectorPath, 2, false); } if (node == null) { throw new IllegalStateException("Unexpected exception. Failed to locate the node '" + selectorPath + "'."); } return node; } protected void assertSelectorOK(ResourceConfig resourceConfig) { String selector = resourceConfig.getSelectorPath().getSelector(); if (selector != null) { if (selector.contains(ResourceConfig.DOCUMENT_FRAGMENT_SELECTOR)) { throw new SmooksConfigException("Cannot use the document selector with the XMLBinding class. Must use an absolute path. Selector value '" + selector + "'."); } if (!selector.startsWith("/") && !selector.startsWith(" ${") && !selector.startsWith("#")) { throw new SmooksConfigException("Invalid selector value '" + selector + "'. Selector paths must be absolute."); } rootElementNames.add(((ElementSelectorStep) resourceConfig.getSelectorPath().get(1)).getQName()); } } protected static class RootNodeSerializer { protected final String beanId; protected final XMLElementSerializationNode serializer; protected RootNodeSerializer(String beanId, XMLElementSerializationNode serializer) { this.beanId = beanId; this.serializer = serializer; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy