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

org.hibernate.boot.jaxb.internal.stax.JpaOrmXmlEventReader Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.boot.jaxb.internal.stax;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;

import org.hibernate.boot.xsd.LocalXsdResolver;
import org.hibernate.boot.xsd.MappingXsdSupport;

/**
 * A JPA {@code orm.xml} specific StAX EVentReader to handle a few oddities.
 *
 * Mainly we handle the namespace change.
 *
 * Ultimately we should handle "upgrading" the documents as well.  The idea being that
 * we'd always treat all versions as the latest.
 *
 * {@see HHH-8108} for more discussion.
 *
 * @author Strong Liu
 * @author Steve Ebersole
 * @author Hardy Ferentschik
 */
public class JpaOrmXmlEventReader extends EventReaderDelegate {
	private static final List NAMESPACE_URIS_TO_MAP = Arrays.asList(
			// JPA 1.0 and 2.0 namespace uri
			"http://java.sun.com/xml/ns/persistence/orm"
	);

	private static final String ROOT_ELEMENT_NAME = "entity-mappings";
	private static final String VERSION_ATTRIBUTE_NAME = "version";

	private final XMLEventFactory xmlEventFactory;

	public JpaOrmXmlEventReader(XMLEventReader reader) {
		this( reader, XMLEventFactory.newInstance() );
	}

	public JpaOrmXmlEventReader(XMLEventReader reader, XMLEventFactory xmlEventFactory) {
		super( reader );
		this.xmlEventFactory = xmlEventFactory;
	}

	@Override
	public XMLEvent peek() throws XMLStreamException {
		return wrap( super.peek() );
	}

	@Override
	public XMLEvent nextEvent() throws XMLStreamException {
		return wrap( super.nextEvent() );
	}

	private XMLEvent wrap(XMLEvent event) {
		if ( event != null ) {
			if ( event.isStartElement() ) {
				return wrap( event.asStartElement() );
			}
			else if ( event.isEndElement() ) {
				return wrap( event.asEndElement() );
			}
		}
		return event;
	}

	private StartElement wrap(StartElement startElement) {
		List newElementAttributeList = mapAttributes( startElement );
		List newNamespaceList = mapNamespaces( startElement );

		// Transfer the location info from the incoming event to the event factory
		// so that the event we ask it to generate for us has the same location info
		xmlEventFactory.setLocation( startElement.getLocation() );
		return xmlEventFactory.createStartElement(
				new QName( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri(), startElement.getName().getLocalPart() ),
				newElementAttributeList.iterator(),
				newNamespaceList.iterator()
		);
	}

	private List mapAttributes(StartElement startElement) {
		final List mappedAttributes = new ArrayList<>();

		Iterator existingAttributesIterator = existingXmlAttributesIterator( startElement );
		while ( existingAttributesIterator.hasNext() ) {
			final Attribute originalAttribute = existingAttributesIterator.next();
			final Attribute attributeToUse = mapAttribute( startElement, originalAttribute );
			mappedAttributes.add( attributeToUse );
		}

		return mappedAttributes;
	}

	@SuppressWarnings("unchecked")
	private Iterator existingXmlAttributesIterator(StartElement startElement) {
		return startElement.getAttributes();
	}

	private Attribute mapAttribute(StartElement startElement, Attribute originalAttribute) {
		// Here we look to see if this attribute is the JPA version attribute, and if so do 2 things:
		//		1) validate its version attribute is valid
		//		2) update its version attribute to the default version if not already
		//
		// NOTE : atm this is a very simple check using just the attribute's local name
		// rather than checking its qualified name.  It is possibly (though unlikely)
		// that this could match on "other" version attributes in the same element

		if ( ROOT_ELEMENT_NAME.equals( startElement.getName().getLocalPart() ) ) {
			if ( VERSION_ATTRIBUTE_NAME.equals( originalAttribute.getName().getLocalPart() ) ) {
				final String specifiedVersion = originalAttribute.getValue();

				if ( !LocalXsdResolver.isValidJpaVersion( specifiedVersion ) ) {
					throw new BadVersionException( specifiedVersion );
				}

				return xmlEventFactory.createAttribute( VERSION_ATTRIBUTE_NAME, LocalXsdResolver.latestJpaVerison() );
			}
		}

		return originalAttribute;
	}

	private List mapNamespaces(StartElement startElement) {
		return mapNamespaces( existingXmlNamespacesIterator( startElement ) );
	}

	private List mapNamespaces(Iterator originalNamespaceIterator ) {
		final List mappedNamespaces = new ArrayList();

//		final String elementNamespacePrefix = startElement.getName().getPrefix();
//		if ( EMPTY_NAMESPACE_PREFIX.equals( elementNamespacePrefix ) ) {
//			// add the default namespace mapping
//			mappedNamespaces.add( xmlEventFactory.createNamespace( LocalSchema.ORM.getNamespaceUri() ) );
//		}

		while ( originalNamespaceIterator.hasNext() ) {
			final Namespace originalNamespace  = originalNamespaceIterator.next();
			final Namespace mappedNamespace = mapNamespace( originalNamespace );
			mappedNamespaces.add( mappedNamespace );
		}

		if ( mappedNamespaces.isEmpty() ) {
			mappedNamespaces.add( xmlEventFactory.createNamespace( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri() ) );
		}

		return mappedNamespaces;
	}

	@SuppressWarnings("unchecked")
	private Iterator existingXmlNamespacesIterator(StartElement startElement) {
		return startElement.getNamespaces();
	}

	private Namespace mapNamespace(Namespace originalNamespace) {
		if ( NAMESPACE_URIS_TO_MAP.contains( originalNamespace.getNamespaceURI() ) ) {
			// this is a namespace "to map" so map it
			return xmlEventFactory.createNamespace( originalNamespace.getPrefix(), MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri() );
		}

		return originalNamespace;
	}

	private XMLEvent wrap(EndElement endElement) {
		final List targetNamespaces = mapNamespaces( existingXmlNamespacesIterator( endElement ) );

		// Transfer the location info from the incoming event to the event factory
		// so that the event we ask it to generate for us has the same location info
		xmlEventFactory.setLocation( endElement.getLocation() );
		return xmlEventFactory.createEndElement(
				new QName( MappingXsdSupport.INSTANCE.latestJpaDescriptor().getNamespaceUri(), endElement.getName().getLocalPart() ),
				targetNamespaces.iterator()
		);
	}

	@SuppressWarnings("unchecked")
	private Iterator existingXmlNamespacesIterator(EndElement endElement) {
		return endElement.getNamespaces();
	}

	public static class BadVersionException extends RuntimeException {
		private final String requestedVersion;

		public BadVersionException(String requestedVersion) {
			this.requestedVersion = requestedVersion;
		}

		public String getRequestedVersion() {
			return requestedVersion;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy