diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index 3f65833c23..0bb0b8f4c5 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -84,7 +84,7 @@ task jaxb { // input schemas cfgXsd = file( 'src/main/resources/org/hibernate/hibernate-configuration-4.0.xsd') hbmXsd = file( 'src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd' ) - ormXsd = file( 'src/main/resources/org/hibernate/ejb/orm_2_0.xsd' ) + ormXsd = file( 'src/main/resources/org/hibernate/jpa/orm_2_1.xsd' ) // input bindings cfgXjb = file( 'src/main/xjb/hbm-configuration-bindings.xjb' ) diff --git a/hibernate-core/src/main/java/org/hibernate/InvalidMappingException.java b/hibernate-core/src/main/java/org/hibernate/InvalidMappingException.java index 59a1d862a0..c4e461cb7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/InvalidMappingException.java +++ b/hibernate-core/src/main/java/org/hibernate/InvalidMappingException.java @@ -49,26 +49,34 @@ public class InvalidMappingException extends MappingException { this.path=path; } - public InvalidMappingException(String customMessage, XmlDocument xmlDocument, Throwable cause) { - this( customMessage, xmlDocument.getOrigin().getType(), xmlDocument.getOrigin().getName(), cause ); - } - - public InvalidMappingException(String customMessage, XmlDocument xmlDocument) { - this( customMessage, xmlDocument.getOrigin().getType(), xmlDocument.getOrigin().getName() ); - } - - public InvalidMappingException(String customMessage, Origin origin) { - this( customMessage, origin.getType().toString(), origin.getName() ); - } - - public InvalidMappingException(String type, String path) { - this("Could not parse mapping document from " + type + (path==null?"":" " + path), type, path); - } +// public InvalidMappingException(String customMessage, XmlDocument xmlDocument, Throwable cause) { +// this( customMessage, xmlDocument.getOrigin().getType(), xmlDocument.getOrigin().getName(), cause ); +// } +// +// public InvalidMappingException(String customMessage, XmlDocument xmlDocument) { +// this( customMessage, xmlDocument.getOrigin().getType(), xmlDocument.getOrigin().getName() ); +// } +// +// public InvalidMappingException(String customMessage, Origin origin) { +// this( customMessage, origin.getType().toString(), origin.getName() ); +// } +// +// public InvalidMappingException(String type, String path) { +// this("Could not parse mapping document from " + type + (path==null?"":" " + path), type, path); +// } public InvalidMappingException(String type, String path, Throwable cause) { this("Could not parse mapping document from " + type + (path==null?"":" " + path), type, path, cause); } + public InvalidMappingException(String message, Origin origin, Exception cause) { + this( message, origin.getType().name(), origin.getName(), cause ); + } + + public InvalidMappingException(String message, Origin origin) { + this( message, origin, null ); + } + public String getType() { return type; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 5147b9e066..2c36c31f1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -155,6 +155,7 @@ import org.hibernate.id.SequenceHiLoGenerator; import org.hibernate.id.TableHiLoGenerator; import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.Component; import org.hibernate.mapping.DependantValue; @@ -2090,12 +2091,12 @@ public final class AnnotationBinder { if ( naturalIdAnn != null ) { if ( joinColumns != null ) { for ( Ejb3Column column : joinColumns ) { - column.addUniqueKey( "_UniqueKey", inSecondPass ); + column.addUniqueKey( StringHelper.randomFixedLengthHex("UK_"), inSecondPass ); } } else { for ( Ejb3Column column : columns ) { - column.addUniqueKey( "_UniqueKey", inSecondPass ); + column.addUniqueKey( StringHelper.randomFixedLengthHex("UK_"), inSecondPass ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index 8afa907d9e..6f83f594de 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -112,11 +112,11 @@ import org.hibernate.internal.util.collections.JoinedIterator; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.internal.util.xml.ErrorLogger; import org.hibernate.internal.util.xml.MappingReader; -import org.hibernate.internal.util.xml.Origin; -import org.hibernate.internal.util.xml.OriginImpl; import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.internal.util.xml.XmlDocument; import org.hibernate.internal.util.xml.XmlDocumentImpl; +import org.hibernate.jaxb.spi.Origin; +import org.hibernate.jaxb.spi.SourceType; import org.hibernate.mapping.AuxiliaryDatabaseObject; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; @@ -473,12 +473,12 @@ public class Configuration implements Serializable { catch ( FileNotFoundException e ) { throw new MappingNotFoundException( "file", xmlFile.toString() ); } - add( inputSource, "file", name ); + add( inputSource, SourceType.FILE, name ); return this; } - private XmlDocument add(InputSource inputSource, String originType, String originName) { - return add( inputSource, new OriginImpl( originType, originName ) ); + private XmlDocument add(InputSource inputSource, SourceType originType, String originName) { + return add( inputSource, new Origin( originType, originName ) ); } private XmlDocument add(InputSource inputSource, Origin origin) { @@ -549,7 +549,7 @@ public class Configuration implements Serializable { } LOG.readingMappingsFromFile( xmlFile.getPath() ); - XmlDocument metadataXml = add( inputSource, "file", name ); + XmlDocument metadataXml = add( inputSource, SourceType.FILE, name ); try { LOG.debugf( "Writing cache file for: %s to: %s", xmlFile, cachedFile ); @@ -592,7 +592,7 @@ public class Configuration implements Serializable { LOG.readingCachedMappings( cachedFile ); Document document = ( Document ) SerializationHelper.deserialize( new FileInputStream( cachedFile ) ); - add( new XmlDocumentImpl( document, "file", xmlFile.getAbsolutePath() ) ); + add( new XmlDocumentImpl( document, SourceType.FILE, xmlFile.getAbsolutePath() ) ); return this; } @@ -622,7 +622,7 @@ public class Configuration implements Serializable { public Configuration addXML(String xml) throws MappingException { LOG.debugf( "Mapping XML:\n%s", xml ); final InputSource inputSource = new InputSource( new StringReader( xml ) ); - add( inputSource, "string", "XML String" ); + add( inputSource, SourceType.STRING, "XML String" ); return this; } @@ -640,7 +640,7 @@ public class Configuration implements Serializable { LOG.debugf( "Reading mapping document from URL : %s", urlExternalForm ); try { - add( url.openStream(), "URL", urlExternalForm ); + add( url.openStream(), SourceType.URL, urlExternalForm ); } catch ( IOException e ) { throw new InvalidMappingException( "Unable to open url stream [" + urlExternalForm + "]", "URL", urlExternalForm, e ); @@ -648,7 +648,7 @@ public class Configuration implements Serializable { return this; } - private XmlDocument add(InputStream inputStream, final String type, final String name) { + private XmlDocument add(InputStream inputStream, final SourceType type, final String name) { final InputSource inputSource = new InputSource( inputStream ); try { return add( inputSource, type, name ); @@ -675,7 +675,7 @@ public class Configuration implements Serializable { LOG.debugf( "Mapping Document:\n%s", doc ); final Document document = xmlHelper.createDOMReader().read( doc ); - add( new XmlDocumentImpl( document, "unknown", null ) ); + add( new XmlDocumentImpl( document, SourceType.DOM, null ) ); return this; } @@ -689,7 +689,7 @@ public class Configuration implements Serializable { * processing the contained mapping document. */ public Configuration addInputStream(InputStream xmlInputStream) throws MappingException { - add( xmlInputStream, "input stream", null ); + add( xmlInputStream, SourceType.INPUT_STREAM, null ); return this; } @@ -708,7 +708,7 @@ public class Configuration implements Serializable { if ( resourceInputStream == null ) { throw new MappingNotFoundException( "resource", resourceName ); } - add( resourceInputStream, "resource", resourceName ); + add( resourceInputStream, SourceType.RESOURCE, resourceName ); return this; } @@ -734,7 +734,7 @@ public class Configuration implements Serializable { if ( resourceInputStream == null ) { throw new MappingNotFoundException( "resource", resourceName ); } - add( resourceInputStream, "resource", resourceName ); + add( resourceInputStream, SourceType.RESOURCE, resourceName ); return this; } @@ -1186,6 +1186,21 @@ public class Configuration implements Serializable { table.isQuoted() ); + Iterator uniqueIter = table.getUniqueKeyIterator(); + while ( uniqueIter.hasNext() ) { + final UniqueKey uniqueKey = (UniqueKey) uniqueIter.next(); + // Skip if index already exists + if ( tableInfo != null && StringHelper.isNotEmpty( uniqueKey.getName() ) ) { + final IndexMetadata meta = tableInfo.getIndexMetadata( uniqueKey.getName() ); + if ( meta != null ) { + continue; + } + } + String constraintString = uniqueKey.sqlCreateString( dialect, + mapping, tableCatalog, tableSchema ); + if (constraintString != null) script.add( constraintString ); + } + if ( dialect.hasAlterTable() ) { Iterator subIter = table.getForeignKeyIterator(); while ( subIter.hasNext() ) { @@ -1378,11 +1393,9 @@ public class Configuration implements Serializable { for ( Map.Entry> tableListEntry : uniqueConstraintHoldersByTable.entrySet() ) { final Table table = tableListEntry.getKey(); final List uniqueConstraints = tableListEntry.getValue(); - int uniqueIndexPerTable = 0; for ( UniqueConstraintHolder holder : uniqueConstraints ) { - uniqueIndexPerTable++; final String keyName = StringHelper.isEmpty( holder.getName() ) - ? "UK_" + table.getName() + "_" + uniqueIndexPerTable + ? StringHelper.randomFixedLengthHex("UK_") : holder.getName(); buildUniqueKeyFromColumnNames( table, keyName, holder.getColumns() ); } @@ -3537,7 +3550,7 @@ public class Configuration implements Serializable { } catch ( MappingException me ) { throw new InvalidMappingException( - metadataXml.getOrigin().getType(), + metadataXml.getOrigin().getType().name(), metadataXml.getOrigin().getName(), me ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java index 4eff1d24bb..78d21b3148 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java @@ -56,37 +56,57 @@ public class EJB3DTDEntityResolver extends DTDEntityResolver { @Override public InputSource resolveEntity(String publicId, String systemId) { LOG.tracev( "Resolving XML entity {0} : {1}", publicId, systemId ); - InputSource is = super.resolveEntity( publicId, systemId ); - if ( is == null ) { - if ( systemId != null ) { - if ( systemId.endsWith( "orm_1_0.xsd" ) ) { - InputStream dtdStream = getStreamFromClasspath( "orm_1_0.xsd" ); - final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); - if (source != null) return source; + if ( systemId != null ) { + if ( systemId.endsWith( "orm_2_1.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_2_1.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; } - else if ( systemId.endsWith( "orm_2_0.xsd" ) ) { - InputStream dtdStream = getStreamFromClasspath( "orm_2_0.xsd" ); - final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); - if (source != null) return source; + } + else if ( systemId.endsWith( "orm_2_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_2_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; } - else if ( systemId.endsWith( "persistence_1_0.xsd" ) ) { - InputStream dtdStream = getStreamFromClasspath( "persistence_1_0.xsd" ); - final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); - if (source != null) return source; + } + else if ( systemId.endsWith( "orm_1_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_1_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; } - else if ( systemId.endsWith( "persistence_2_0.xsd" ) ) { - InputStream dtdStream = getStreamFromClasspath( "persistence_2_0.xsd" ); - final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); - if (source != null) return source; + } + else if ( systemId.endsWith( "persistence_2_1.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_2_1.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "persistence_2_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_2_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "persistence_1_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_1_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; } } } - else { + + // because the old code did this too (in terms of setting resolved) + InputSource source = super.resolveEntity( publicId, systemId ); + if ( source != null ) { resolved = true; - return is; } - //use the default behavior - return null; + return source; } private InputSource buildInputSource(String publicId, String systemId, InputStream dtdStream, boolean resolved) { @@ -103,8 +123,8 @@ public class EJB3DTDEntityResolver extends DTDEntityResolver { } private InputStream getStreamFromClasspath(String fileName) { - LOG.trace( "Recognized JPA ORM namespace; attempting to resolve on classpath under org/hibernate/ejb" ); - String path = "org/hibernate/ejb/" + fileName; + LOG.trace( "Recognized JPA ORM namespace; attempting to resolve on classpath under org/hibernate/jpa" ); + String path = "org/hibernate/jpa/" + fileName; InputStream dtdStream = resolveInHibernateNamespace( path ); return dtdStream; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java index 52eefa3abf..2ddff1e0d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java @@ -2247,7 +2247,7 @@ public final class HbmBinder { } else if ( "natural-id".equals( name ) ) { UniqueKey uk = new UniqueKey(); - uk.setName("_UniqueKey"); + uk.setName(StringHelper.randomFixedLengthHex("UK_")); uk.setTable(table); //by default, natural-ids are "immutable" (constant) boolean mutableId = "true".equals( subnode.attributeValue("mutable") ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java index f16bb78eb2..8db6de8449 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java @@ -65,10 +65,12 @@ import javax.persistence.ExcludeDefaultListeners; import javax.persistence.ExcludeSuperclassListeners; import javax.persistence.FetchType; import javax.persistence.FieldResult; +import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.IdClass; +import javax.persistence.Index; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; @@ -227,6 +229,8 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotationToXml.put( MapKeyJoinColumns.class, "map-key-join-column" ); annotationToXml.put( OrderColumn.class, "order-column" ); annotationToXml.put( Cacheable.class, "cacheable" ); + annotationToXml.put( Index.class, "index" ); + annotationToXml.put( ForeignKey.class, "foreign-key" ); } private XMLContext xmlContext; @@ -659,6 +663,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotation.setValue( "schema", defaults.getSchema() ); } buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); annotation.setValue( "joinColumns", getJoinColumns( subelement, false ) ); annotation.setValue( "inverseJoinColumns", getJoinColumns( subelement, true ) ); return AnnotationFactory.create( annotation ); @@ -1069,6 +1074,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotation.setValue( "joinColumns", joinColumns ); } buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); annotationList.add( AnnotationFactory.create( annotation ) ); } } @@ -2298,6 +2304,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotation.setValue( "schema", defaults.getSchema() ); } buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); return AnnotationFactory.create( annotation ); } } @@ -2321,6 +2328,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotation.setValue( "schema", defaults.getSchema() ); } buildUniqueConstraints( annotation, element ); + buildIndex( annotation, element ); annotation.setValue( "pkJoinColumns", buildPrimaryKeyJoinColumns( element ) ); secondaryTables.add( (SecondaryTable) AnnotationFactory.create( annotation ) ); } @@ -2376,7 +2384,19 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { } } } - + private static void buildIndex(AnnotationDescriptor annotation, Element element){ + List indexElementList = element.elements( "index" ); + Index[] indexes = new Index[indexElementList.size()]; + for(int i=0;i length) { + s = s.substring( 0, length ); + } + return prefix + s; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BaseXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BaseXMLEventReader.java new file mode 100644 index 0000000000..e77971bf4b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BaseXMLEventReader.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.EntityDeclaration; +import javax.xml.stream.events.EntityReference; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.EventReaderDelegate; + +/** + * Base for XMLEventReader that implements the {@link #getElementText()} and {@link #nextTag()} APIs in a + * way that is agnostic from the rest of the XMLEventReader implementation. Both will use the subclasses + * {@link #internalNextEvent()} as the exclusive way to read events. + * + * Note, copied from the uPortal project by permission of author. See + * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/BaseXMLEventReader.java + * + * @author Eric Dalquist + */ +public abstract class BaseXMLEventReader extends EventReaderDelegate { + private XMLEvent previousEvent; + + public BaseXMLEventReader(XMLEventReader reader) { + super(reader); + } + + /** + * Subclass's version of {@link #nextEvent()}, called by {@link #next()} + */ + protected abstract XMLEvent internalNextEvent() throws XMLStreamException; + + /** + * @return The XMLEvent returned by the last call to {@link #internalNextEvent()} + */ + protected final XMLEvent getPreviousEvent() { + return this.previousEvent; + } + + @Override + public final XMLEvent nextEvent() throws XMLStreamException { + this.previousEvent = this.internalNextEvent(); + return this.previousEvent; + } + + /* (non-Javadoc) + * @see java.util.Iterator#next() + */ + @Override + public final Object next() { + try { + return this.nextEvent(); + } + catch (XMLStreamException e) { + return null; + } + } + + /* (non-Javadoc) + * @see javax.xml.stream.XMLEventReader#getElementText() + */ + @Override + public final String getElementText() throws XMLStreamException { + XMLEvent event = this.previousEvent; + if (event == null) { + throw new XMLStreamException("Must be on START_ELEMENT to read next text, element was null"); + } + if (!event.isStartElement()) { + throw new XMLStreamException("Must be on START_ELEMENT to read next text", event.getLocation()); + } + + final StringBuilder text = new StringBuilder(); + while (!event.isEndDocument()) { + switch (event.getEventType()) { + case XMLStreamConstants.CHARACTERS: + case XMLStreamConstants.SPACE: + case XMLStreamConstants.CDATA: { + final Characters characters = event.asCharacters(); + text.append(characters.getData()); + break; + } + case XMLStreamConstants.ENTITY_REFERENCE: { + final EntityReference entityReference = (EntityReference)event; + final EntityDeclaration declaration = entityReference.getDeclaration(); + text.append(declaration.getReplacementText()); + break; + } + case XMLStreamConstants.COMMENT: + case XMLStreamConstants.PROCESSING_INSTRUCTION: { + //Ignore + break; + } + default: { + throw new XMLStreamException("Unexpected event type '" + XMLStreamConstantsUtils.getEventName(event.getEventType()) + "' encountered. Found event: " + event, event.getLocation()); + } + } + + event = this.nextEvent(); + } + + return text.toString(); + } + + /* (non-Javadoc) + * @see javax.xml.stream.XMLEventReader#nextTag() + */ + @Override + public final XMLEvent nextTag() throws XMLStreamException { + XMLEvent event = this.nextEvent(); + while ((event.isCharacters() && event.asCharacters().isWhiteSpace()) + || event.isProcessingInstruction() + || event.getEventType() == XMLStreamConstants.COMMENT) { + + event = this.nextEvent(); + } + + if (!event.isStartElement() && event.isEndElement()) { + throw new XMLStreamException("Unexpected event type '" + XMLStreamConstantsUtils.getEventName(event.getEventType()) + "' encountered. Found event: " + event, event.getLocation()); + } + + return event; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java new file mode 100644 index 0000000000..529143c27f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/BufferedXMLEventReader.java @@ -0,0 +1,196 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +/** + * Buffers XML events for later re-reading + * + * Note, copied from the uPortal project by permission of author. See + * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/BufferedXMLEventReader.java + * + * @author Eric Dalquist + */ +public class BufferedXMLEventReader extends BaseXMLEventReader { + private final LinkedList eventBuffer = new LinkedList(); + private int eventLimit = 0; + private ListIterator bufferReader = null; + + /** + * Create new buffering reader, no buffering is done until {@link #mark(int)} is called. + */ + public BufferedXMLEventReader(XMLEventReader reader) { + super(reader); + } + + /** + * Create new buffering reader. Calls {@link #mark(int)} with the specified event limit + * @see #mark(int) + */ + public BufferedXMLEventReader(XMLEventReader reader, int eventLimit) { + super(reader); + this.eventLimit = eventLimit; + } + + /** + * @return A copy of the current buffer + */ + public List getBuffer() { + return new ArrayList(this.eventBuffer); + } + + /* (non-Javadoc) + * @see org.jasig.portal.xml.stream.BaseXMLEventReader#internalNextEvent() + */ + @Override + protected XMLEvent internalNextEvent() throws XMLStreamException { + //If there is an iterator to read from reset was called, use the iterator + //until it runs out of events. + if (this.bufferReader != null) { + final XMLEvent event = this.bufferReader.next(); + + //If nothing left in the iterator, remove the reference and fall through to direct reading + if (!this.bufferReader.hasNext()) { + this.bufferReader = null; + } + + return event; + } + + //Get the next event from the underlying reader + final XMLEvent event = this.getParent().nextEvent(); + + //if buffering add the event + if (this.eventLimit != 0) { + this.eventBuffer.offer(event); + + //If limited buffer size and buffer is too big trim the buffer. + if (this.eventLimit > 0 && this.eventBuffer.size() > this.eventLimit) { + this.eventBuffer.poll(); + } + } + + return event; + } + + @Override + public boolean hasNext() { + return this.bufferReader != null || super.hasNext(); + } + + @Override + public XMLEvent peek() throws XMLStreamException { + if (this.bufferReader != null) { + final XMLEvent event = this.bufferReader.next(); + this.bufferReader.previous(); //move the iterator back + return event; + } + return super.peek(); + } + + /** + * Same as calling {@link #mark(int)} with -1. + */ + public void mark() { + this.mark(-1); + } + + /** + * Start buffering events + * @param eventLimit the maximum number of events to buffer. -1 will buffer all events, 0 will buffer no events. + */ + public void mark(int eventLimit) { + this.eventLimit = eventLimit; + + //Buffering no events now, clear the buffer and buffered reader + if (this.eventLimit == 0) { + this.eventBuffer.clear(); + this.bufferReader = null; + } + //Buffering limited set of events, lets trim the buffer if needed + else if (this.eventLimit > 0) { + //If there is an iterator check its current position and calculate the new iterator start position + int iteratorIndex = 0; + if (this.bufferReader != null) { + final int nextIndex = this.bufferReader.nextIndex(); + iteratorIndex = Math.max(0, nextIndex - (this.eventBuffer.size() - this.eventLimit)); + } + + //Trim the buffer until it is not larger than the limit + while (this.eventBuffer.size() > this.eventLimit) { + this.eventBuffer.poll(); + } + + //If there is an iterator re-create it using the newly calculated index + if (this.bufferReader != null) { + this.bufferReader = this.eventBuffer.listIterator(iteratorIndex); + } + } + } + + /** + * Reset the reader to these start of the buffered events. + */ + public void reset() { + if (this.eventBuffer.isEmpty()) { + this.bufferReader = null; + } + else { + this.bufferReader = this.eventBuffer.listIterator(); + } + } + + @Override + public void close() throws XMLStreamException { + this.mark(0); + super.close(); + } + + /** + * @return The number of events in the buffer. + */ + public int bufferSize() { + return this.eventBuffer.size(); + } + + /** + * If reading from the buffer after a {@link #reset()} call an {@link IllegalStateException} will be thrown. + */ + @Override + public void remove() { + if (this.bufferReader != null && this.bufferReader.hasNext()) { + throw new IllegalStateException("Cannot remove a buffered element"); + } + + super.remove(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/ErrorLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/ErrorLogger.java index acbb0762a2..fceacce7f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/ErrorLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/ErrorLogger.java @@ -58,9 +58,7 @@ public class ErrorLogger implements ErrorHandler, Serializable { this.file = file; } - /** - * {@inheritDoc} - */ + @Override public void error(SAXParseException error) { if ( this.errors == null ) { errors = new ArrayList(); @@ -68,23 +66,16 @@ public class ErrorLogger implements ErrorHandler, Serializable { errors.add( error ); } - /** - * {@inheritDoc} - */ + @Override public void fatalError(SAXParseException error) { error( error ); } - /** - * {@inheritDoc} - */ + @Override public void warning(SAXParseException warn) { LOG.parsingXmlWarning( warn.getLineNumber(), warn.getMessage() ); } - /** - * @return returns a list of encountered xml parsing errors, or the empty list if there was no error - */ public List getErrors() { return errors; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java new file mode 100644 index 0000000000..5d115f31dd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/FilteringXMLEventReader.java @@ -0,0 +1,141 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +/** + * Base class for {@link XMLEventReader}s that want to modify or remove events from the reader stream. + * If a {@link StartElement} event is removed the subclass's {@link #filterEvent(XMLEvent, boolean)} will + * not see any events until after the matching {@link EndElement} event. + * + * Note, copied from the uPortal project by permission of author. See + * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/FilteringXMLEventReader.java + * + * @author Eric Dalquist + */ +public abstract class FilteringXMLEventReader extends BaseXMLEventReader { + private final Deque prunedElements = new LinkedList(); + private XMLEvent peekedEvent = null; + + public FilteringXMLEventReader(XMLEventReader reader) { + super(reader); + } + + @Override + protected final XMLEvent internalNextEvent() throws XMLStreamException { + return this.internalNext(false); + } + + @Override + public boolean hasNext() { + try { + return peekedEvent != null || (super.hasNext() && this.peek() != null); + } + catch (XMLStreamException e) { + throw new RuntimeException(e.getMessage(), e); + } + catch (NoSuchElementException e) { + return false; + } + } + + @Override + public final XMLEvent peek() throws XMLStreamException { + if (peekedEvent != null) { + return peekedEvent; + } + + peekedEvent = internalNext(true); + return peekedEvent; + } + + protected final XMLEvent internalNext(boolean peek) throws XMLStreamException { + XMLEvent event = null; + + if (peekedEvent != null) { + event = peekedEvent; + peekedEvent = null; + return event; + } + + do { + event = super.getParent().nextEvent(); + + //If there are pruned elements in the queue filtering events is still needed + if (!prunedElements.isEmpty()) { + //If another start element add it to the queue + if (event.isStartElement()) { + final StartElement startElement = event.asStartElement(); + prunedElements.push(startElement.getName()); + } + //If end element pop the newest name of the queue and double check that the start/end elements match up + else if (event.isEndElement()) { + final QName startElementName = prunedElements.pop(); + + final EndElement endElement = event.asEndElement(); + final QName endElementName = endElement.getName(); + + if (!startElementName.equals(endElementName)) { + throw new IllegalArgumentException("Malformed XMLEvent stream. Expected end element for " + startElementName + " but found end element for " + endElementName); + } + } + + event = null; + } + else { + final XMLEvent filteredEvent = this.filterEvent(event, peek); + + //If the event is being removed and it is a start element all elements until the matching + //end element need to be removed as well + if (filteredEvent == null && event.isStartElement()) { + final StartElement startElement = event.asStartElement(); + final QName name = startElement.getName(); + prunedElements.push(name); + } + + event = filteredEvent; + } + } + while (event == null); + + return event; + } + + /** + * @param event The current event + * @param peek If the event is from a {@link #peek()} call + * @return The event to return, if null is returned the event is dropped from the stream and the next event will be used. + */ + protected abstract XMLEvent filterEvent(XMLEvent event, boolean peek); +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/LocalXmlResourceResolver.java new file mode 100644 index 0000000000..a442963248 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/LocalXmlResourceResolver.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import javax.xml.stream.XMLResolver; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.xml.sax.InputSource; + +import org.jboss.logging.Logger; + +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ConfigHelper; + +/** + * @author Steve Ebersole + */ +public class LocalXmlResourceResolver implements javax.xml.stream.XMLResolver { + private static final CoreMessageLogger log = Logger.getMessageLogger( + CoreMessageLogger.class, + MappingReader.class.getName() + ); + + public static final LocalXmlResourceResolver INSTANCE = new LocalXmlResourceResolver(); + + /** + * Namespace for the orm.xml xsd for jpa 1.0 and 2.0 + */ + public static final String INITIAL_JPA_ORM_NS = "http://java.sun.com/xml/ns/persistence/orm"; + + /** + * Namespace for the orm.xml xsd for jpa 2.1 + */ + public static final String SECOND_JPA_ORM_NS = "http://xmlns.jcp.org/xml/ns/persistence/orm"; + + public static final String HIBERNATE_MAPPING_DTD_URL_BASE = "http://www.hibernate.org/dtd/"; + public static final String LEGACY_HIBERNATE_MAPPING_DTD_URL_BASE = "http://hibernate.sourceforge.net/"; + public static final String CLASSPATH_EXTENSION_URL_BASE = "classpath://"; + + @Override + public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace) throws XMLStreamException { + log.tracef( "In resolveEntity(%s, %s, %s, %s)", publicID, systemID, baseURI, namespace ); + + if ( namespace != null ) { + log.debugf( "Interpreting namespace : %s", namespace ); + if ( INITIAL_JPA_ORM_NS.equals( namespace ) ) { + return openUrlStream( MappingReader.SupportedOrmXsdVersion.ORM_2_0.getSchemaUrl() ); + } + else if ( SECOND_JPA_ORM_NS.equals( namespace ) ) { + return openUrlStream( MappingReader.SupportedOrmXsdVersion.ORM_2_1.getSchemaUrl() ); + } + } + + if ( systemID != null ) { + log.debugf( "Interpreting systemID : %s", namespace ); + InputStream stream = null; + if ( systemID.startsWith( HIBERNATE_MAPPING_DTD_URL_BASE ) ) { + log.debug( "Recognized hibernate namespace; attempting to resolve on classpath under org/hibernate/" ); + stream = resolveOnClassPath( systemID, HIBERNATE_MAPPING_DTD_URL_BASE ); + } + else if ( systemID.startsWith( LEGACY_HIBERNATE_MAPPING_DTD_URL_BASE ) ) { + log.recognizedObsoleteHibernateNamespace( LEGACY_HIBERNATE_MAPPING_DTD_URL_BASE, HIBERNATE_MAPPING_DTD_URL_BASE ); + log.debug( "Attempting to resolve on classpath under org/hibernate/" ); + stream = resolveOnClassPath( systemID, LEGACY_HIBERNATE_MAPPING_DTD_URL_BASE ); + } + else if ( systemID.startsWith( CLASSPATH_EXTENSION_URL_BASE ) ) { + log.debug( "Recognized local namespace; attempting to resolve on classpath" ); + final String path = systemID.substring( CLASSPATH_EXTENSION_URL_BASE.length() ); + stream = resolveInLocalNamespace( path ); + if ( stream == null ) { + log.debugf( "Unable to resolve [%s] on classpath", systemID ); + } + else { + log.debugf( "Resolved [%s] on classpath", systemID ); + } + } + + if ( stream != null ) { + return stream; + } + } + + return null; + } + + private InputStream openUrlStream(URL url) { + try { + return url.openStream(); + } + catch (IOException e) { + throw new XmlInfrastructureException( "Could not open url stream : " + url.toExternalForm(), e ); + } + } + + private InputStream resolveOnClassPath(String systemID, String namespace) { + final String relativeResourceName = systemID.substring( namespace.length() ); + final String path = "org/hibernate/" + relativeResourceName; + InputStream dtdStream = resolveInHibernateNamespace( path ); + if ( dtdStream == null ) { + log.debugf( "Unable to locate [%s] on classpath", systemID ); + if ( relativeResourceName.contains( "2.0" ) ) { + log.usingOldDtd(); + } + return null; + } + else { + log.debugf( "Located [%s] in classpath", systemID ); + return dtdStream; + } + } + + private InputStream resolveInHibernateNamespace(String path) { + return this.getClass().getClassLoader().getResourceAsStream( path ); + } + + private InputStream resolveInLocalNamespace(String path) { + try { + return ConfigHelper.getUserResourceAsStream( path ); + } + catch ( Throwable t ) { + return null; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/MappingReader.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/MappingReader.java index d40e2b3eb4..0331c9e271 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/MappingReader.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/MappingReader.java @@ -23,17 +23,36 @@ */ package org.hibernate.internal.util.xml; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.XMLEvent; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.IOException; +import java.io.InputStream; import java.io.StringReader; +import java.net.URL; import org.dom4j.Document; import org.dom4j.io.SAXReader; -import org.jboss.logging.Logger; +import org.dom4j.io.STAXEventReader; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.jboss.logging.Logger; + import org.hibernate.InvalidMappingException; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jaxb.spi.Origin; /** * Handles reading mapping documents, both {@code hbm} and {@code orm} varieties. @@ -41,7 +60,6 @@ import org.hibernate.internal.CoreMessageLogger; * @author Steve Ebersole */ public class MappingReader { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, MappingReader.class.getName() @@ -59,10 +77,241 @@ public class MappingReader { private MappingReader() { } + public XmlDocument readMappingDocument(InputSource source, Origin origin) { + XMLEventReader staxReader = buildStaxEventReader( source, origin ); + try { + return read( staxReader, origin ); + } + finally { + try { + staxReader.close(); + } + catch ( Exception ignore ) { + } + } + } + + private XMLEventReader buildStaxEventReader(InputSource source, Origin origin) { + XMLEventReader reader = null; + + if ( source.getByteStream() != null ) { + try { + reader = staxFactory().createXMLEventReader( source.getByteStream() ); + } + catch (XMLStreamException e) { + throw new XmlInfrastructureException( + "Unable to create stax reader, origin = " + toLoggableString( origin ), + e + ); + } + } + else if ( source.getCharacterStream() != null ) { + try { + reader = staxFactory().createXMLEventReader( source.getCharacterStream() ); + } + catch (XMLStreamException e) { + throw new XmlInfrastructureException( + "Unable to create stax reader, origin = " + toLoggableString( origin ), + e + ); + } + } + // todo : try to interpret the InputSource SystemId or Origin path? + + if ( reader == null ) { + throw new XmlInfrastructureException( "Unable to convert SAX InputStream into StAX XMLEventReader" ); + } + + // For performance we wrap the reader in a buffered reader + return new BufferedXMLEventReader( reader ); + } + + private XMLInputFactory staxFactory; + + private XMLInputFactory staxFactory() { + if ( staxFactory == null ) { + staxFactory = buildStaxFactory(); + } + return staxFactory; + } + + @SuppressWarnings( { "UnnecessaryLocalVariable" }) + private XMLInputFactory buildStaxFactory() { + XMLInputFactory staxFactory = XMLInputFactory.newInstance(); + staxFactory.setXMLResolver( LocalXmlResourceResolver.INSTANCE ); + return staxFactory; + } + + private String toLoggableString(Origin origin) { + return "[type=" + origin.getType() + ", name=" + origin.getName() + "]"; + } + + private static final QName ORM_VERSION_ATTRIBUTE_QNAME = new QName( "version" ); + + private XmlDocument read(XMLEventReader staxEventReader, Origin origin) { + XMLEvent event; + try { + event = staxEventReader.peek(); + while ( event != null && !event.isStartElement() ) { + staxEventReader.nextEvent(); + event = staxEventReader.peek(); + } + } + catch ( Exception e ) { + throw new InvalidMappingException( "Error accessing stax stream", origin, e ); + } + + if ( event == null ) { + throw new InvalidMappingException( "Could not locate root element", origin ); + } + + final String rootElementName = event.asStartElement().getName().getLocalPart(); + + if ( "entity-mappings".equals( rootElementName ) ) { + final Attribute attribute = event.asStartElement().getAttributeByName( ORM_VERSION_ATTRIBUTE_QNAME ); + final String explicitVersion = attribute == null ? null : attribute.getValue(); + validateMapping( + SupportedOrmXsdVersion.parse( explicitVersion, origin ), + staxEventReader, + origin + ); + } + + return new XmlDocumentImpl( toDom4jDocument( staxEventReader, origin ), origin ); + } + + private Document toDom4jDocument(XMLEventReader staxEventReader, Origin origin) { + STAXEventReader dom4jStaxEventReader = new STAXEventReader(); + try { + // the dom4j converter class is touchy about comments (aka, comments make it implode) + // so wrap the event stream in a filtering stream to filter out comment events + staxEventReader = new FilteringXMLEventReader( staxEventReader ) { + @Override + protected XMLEvent filterEvent(XMLEvent event, boolean peek) { + return event.getEventType() == XMLStreamConstants.COMMENT + ? null + : event; + } + }; + + return dom4jStaxEventReader.readDocument( staxEventReader ); + } + catch (XMLStreamException e) { + throw new InvalidMappingException( "Unable to read StAX source as dom4j Document for processing", origin, e ); + } + } + + public static void validateMapping(SupportedOrmXsdVersion xsdVersion, XMLEventReader staxEventReader, Origin origin) { + final Validator validator = xsdVersion.getSchema().newValidator(); + final StAXSource staxSource; + try { + staxSource = new StAXSource( staxEventReader ); + } + catch (XMLStreamException e) { + throw new InvalidMappingException( "Unable to generate StAXSource from mapping", origin, e ); + } + + try { + validator.validate( staxSource ); + } + catch (SAXException e) { + throw new InvalidMappingException( "SAXException performing validation", origin, e ); + } + catch (IOException e) { + throw new InvalidMappingException( "IOException performing validation", origin, e ); + } + } + + public static enum SupportedOrmXsdVersion { + ORM_1_0( "org/hibernate/jpa/orm_1_0.xsd" ), + ORM_2_0( "org/hibernate/jpa/orm_2_0.xsd" ), + ORM_2_1( "org/hibernate/jpa/orm_2_1.xsd" ), + HBM_4_0( "org/hibernate/hibernate-mapping-4.0.xsd"); + + private final String schemaResourceName; + + private SupportedOrmXsdVersion(String schemaResourceName) { + this.schemaResourceName = schemaResourceName; + } + + public static SupportedOrmXsdVersion parse(String name, Origin origin) { + if ( "1.0".equals( name ) ) { + return ORM_1_0; + } + else if ( "2.0".equals( name ) ) { + return ORM_2_0; + } + else if ( "2.1".equals( name ) ) { + return ORM_2_1; + } + throw new UnsupportedOrmXsdVersionException( name, origin ); + } + + private URL schemaUrl; + + public URL getSchemaUrl() { + if ( schemaUrl == null ) { + schemaUrl = resolveLocalSchemaUrl( schemaResourceName ); + } + return schemaUrl; + } + + private Schema schema; + + public Schema getSchema() { + if ( schema == null ) { + schema = resolveLocalSchema( getSchemaUrl() ); + } + return schema; + } + } + + private static URL resolveLocalSchemaUrl(String schemaName) { + URL url = MappingReader.class.getClassLoader().getResource( schemaName ); + if ( url == null ) { + throw new XmlInfrastructureException( "Unable to locate schema [" + schemaName + "] via classpath" ); + } + return url; + } + + private static Schema resolveLocalSchema(URL schemaUrl) { + + try { + InputStream schemaStream = schemaUrl.openStream(); + try { + StreamSource source = new StreamSource(schemaUrl.openStream()); + SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); + return schemaFactory.newSchema(source); + } + catch ( Exception e ) { + throw new XmlInfrastructureException( "Unable to load schema [" + schemaUrl.toExternalForm() + "]", e ); + } + finally { + try { + schemaStream.close(); + } + catch ( IOException e ) { + LOG.debugf( "Problem closing schema stream - %s", e.toString() ); + } + } + } + catch ( IOException e ) { + throw new XmlInfrastructureException( "Stream error handling schema url [" + schemaUrl.toExternalForm() + "]" ); + } + + } + + public XmlDocument readMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) { + return legacyReadMappingDocument( entityResolver, source, origin ); +// return readMappingDocument( source, origin ); + } + + private XmlDocument legacyReadMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) { // IMPL NOTE : this is the legacy logic as pulled from the old AnnotationConfiguration code Exception failure; + ErrorLogger errorHandler = new ErrorLogger(); SAXReader saxReader = new SAXReader(); @@ -73,193 +322,81 @@ public class MappingReader { Document document = null; try { - // first try with orm 2.0 xsd validation - setValidationFor( saxReader, "orm_2_0.xsd" ); + // first try with orm 2.1 xsd validation + setValidationFor( saxReader, "orm_2_1.xsd" ); document = saxReader.read( source ); if ( errorHandler.hasErrors() ) { throw errorHandler.getErrors().get( 0 ); } - return new XmlDocumentImpl( document, origin.getType(), origin.getName() ); + return new XmlDocumentImpl( document, origin ); } - catch ( Exception orm2Problem ) { + catch ( Exception e ) { if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Problem parsing XML using orm 2 xsd : %s", orm2Problem.getMessage() ); + LOG.debugf( "Problem parsing XML using orm 2.1 xsd, trying 2.0 xsd : %s", e.getMessage() ); } - failure = orm2Problem; + failure = e; errorHandler.reset(); if ( document != null ) { - // next try with orm 1.0 xsd validation + // next try with orm 2.0 xsd validation try { - setValidationFor( saxReader, "orm_1_0.xsd" ); + setValidationFor( saxReader, "orm_2_0.xsd" ); document = saxReader.read( new StringReader( document.asXML() ) ); if ( errorHandler.hasErrors() ) { errorHandler.logErrors(); throw errorHandler.getErrors().get( 0 ); } - return new XmlDocumentImpl( document, origin.getType(), origin.getName() ); + return new XmlDocumentImpl( document, origin ); } - catch ( Exception orm1Problem ) { + catch ( Exception e2 ) { if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Problem parsing XML using orm 1 xsd : %s", orm1Problem.getMessage() ); + LOG.debugf( "Problem parsing XML using orm 2.0 xsd, trying 1.0 xsd : %s", e2.getMessage() ); + } + errorHandler.reset(); + + if ( document != null ) { + // next try with orm 1.0 xsd validation + try { + setValidationFor( saxReader, "orm_1_0.xsd" ); + document = saxReader.read( new StringReader( document.asXML() ) ); + if ( errorHandler.hasErrors() ) { + errorHandler.logErrors(); + throw errorHandler.getErrors().get( 0 ); + } + return new XmlDocumentImpl( document, origin ); + } + catch ( Exception e3 ) { + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Problem parsing XML using orm 1.0 xsd : %s", e3.getMessage() ); + } + } } } } } - throw new InvalidMappingException( "Unable to read XML", origin.getType(), origin.getName(), failure ); + throw new InvalidMappingException( "Unable to read XML", origin, failure ); } private void setValidationFor(SAXReader saxReader, String xsd) { try { saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true ); - //saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true ); - //set the default schema locators - saxReader.setProperty( - "http://apache.org/xml/properties/schema/external-schemaLocation", - "http://java.sun.com/xml/ns/persistence/orm " + xsd - ); + // saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true ); + if ( "orm_2_1.xsd".equals( xsd ) ) { + saxReader.setProperty( + "http://apache.org/xml/properties/schema/external-schemaLocation", + LocalXmlResourceResolver.SECOND_JPA_ORM_NS + " " + xsd + ); + } + else { + saxReader.setProperty( + "http://apache.org/xml/properties/schema/external-schemaLocation", + LocalXmlResourceResolver.INITIAL_JPA_ORM_NS + " " + xsd + ); + } } catch ( SAXException e ) { saxReader.setValidation( false ); } } - // this is the version of the code I'd like to use, but it unfortunately works very differently between - // JDK 1.5 ad JDK 1.6. On 1.5 the vaildation "passes" even with invalid content. - // - // Options: - // 1) continue using the code above - // 2) Document the issue on 1.5 and how to fix (specifying alternate SchemaFactory instance) - // 3) Use a specific JAXP library (Xerces2, Saxon, Jing, MSV) and its SchemaFactory instance directly - -// public XmlDocument readMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) { -// ErrorLogger errorHandler = new ErrorLogger(); -// -// SAXReader saxReader = new SAXReader( new DOMDocumentFactory() ); -// saxReader.setEntityResolver( entityResolver ); -// saxReader.setErrorHandler( errorHandler ); -// saxReader.setMergeAdjacentText( true ); -// -// Document documentTree = null; -// -// // IMPL NOTE : here we enable DTD validation in case the mapping is a HBM file. This will validate -// // the document as it is parsed. This is needed because the DTD defines default values that have to be -// // applied as the document is parsed, so thats something we need to account for as we (if we) transition -// // to XSD. -// saxReader.setValidation( true ); -// try { -// documentTree = saxReader.read( source ); -// } -// catch ( DocumentException e ) { -// // we had issues reading the input, most likely malformed document or validation error against DTD -// throw new InvalidMappingException( "Unable to read XML", origin.getType(), origin.getName(), e ); -// } -// -// Element rootElement = documentTree.getRootElement(); -// if ( rootElement == null ) { -// throw new InvalidMappingException( "No root element", origin.getType(), origin.getName() ); -// } -// -// if ( "entity-mappings".equals( rootElement.getName() ) ) { -// final String explicitVersion = rootElement.attributeValue( "version" ); -// final String xsdVersionString = explicitVersion == null ? ASSUMED_ORM_XSD_VERSION : explicitVersion; -// final SupportedOrmXsdVersion xsdVersion = SupportedOrmXsdVersion.parse( xsdVersionString ); -// final Schema schema = xsdVersion == SupportedOrmXsdVersion.ORM_1_0 ? orm1Schema() : orm2Schema(); -// try { -// schema.newValidator().validate( new DOMSource( (org.w3c.dom.Document) documentTree ) ); -// } -// catch ( SAXException e ) { -// throw new InvalidMappingException( "Validation problem", origin.getType(), origin.getName(), e ); -// } -// catch ( IOException e ) { -// throw new InvalidMappingException( "Validation problem", origin.getType(), origin.getName(), e ); -// } -// } -// else { -// if ( errorHandler.getError() != null ) { -// throw new InvalidMappingException( -// "Error validating hibernate-mapping against DTD", -// origin.getType(), -// origin.getName(), -// errorHandler.getError() -// ); -// } -// } -// -// return new XmlDocumentImpl( documentTree, origin ); -// } -// -// public static enum SupportedOrmXsdVersion { -// ORM_1_0, -// ORM_2_0; -// -// public static SupportedOrmXsdVersion parse(String name) { -// if ( "1.0".equals( name ) ) { -// return ORM_1_0; -// } -// else if ( "2.0".equals( name ) ) { -// return ORM_2_0; -// } -// throw new IllegalArgumentException( "Unsupported orm.xml XSD version encountered [" + name + "]" ); -// } -// } -// -// -// public static final String ORM_1_SCHEMA_NAME = "org/hibernate/ejb/orm_1_0.xsd"; -// public static final String ORM_2_SCHEMA_NAME = "org/hibernate/ejb/orm_2_0.xsd"; -// -// private static Schema orm1Schema; -// -// private static Schema orm1Schema() { -// if ( orm1Schema == null ) { -// orm1Schema = resolveLocalSchema( ORM_1_SCHEMA_NAME ); -// } -// return orm1Schema; -// } -// -// private static Schema orm2Schema; -// -// private static Schema orm2Schema() { -// if ( orm2Schema == null ) { -// orm2Schema = resolveLocalSchema( ORM_2_SCHEMA_NAME ); -// } -// return orm2Schema; -// } -// -// private static Schema resolveLocalSchema(String schemaName) { -// return resolveLocalSchema( schemaName, XMLConstants.W3C_XML_SCHEMA_NS_URI ); -// } -// -// private static Schema resolveLocalSchema(String schemaName, String schemaLanguage) { -// URL url = ConfigHelper.findAsResource( schemaName ); -// if ( url == null ) { -// throw new MappingException( "Unable to locate schema [" + schemaName + "] via classpath" ); -// } -// try { -// InputStream schemaStream = url.openStream(); -// try { -// StreamSource source = new StreamSource(url.openStream()); -// SchemaFactory schemaFactory = SchemaFactory.newInstance( schemaLanguage ); -// return schemaFactory.newSchema(source); -// } -// catch ( SAXException e ) { -// throw new MappingException( "Unable to load schema [" + schemaName + "]", e ); -// } -// catch ( IOException e ) { -// throw new MappingException( "Unable to load schema [" + schemaName + "]", e ); -// } -// finally { -// try { -// schemaStream.close(); -// } -// catch ( IOException e ) { -// log.warn( "Problem closing schema stream [{}]", e.toString() ); -// } -// } -// } -// catch ( IOException e ) { -// throw new MappingException( "Stream error handling schema url [" + url.toExternalForm() + "]" ); -// } -// -// } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/UnsupportedOrmXsdVersionException.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/UnsupportedOrmXsdVersionException.java new file mode 100644 index 0000000000..dcdd80b6d6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/UnsupportedOrmXsdVersionException.java @@ -0,0 +1,43 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import org.hibernate.HibernateException; +import org.hibernate.jaxb.spi.Origin; + +/** + * @author Steve Ebersole + */ +public class UnsupportedOrmXsdVersionException extends HibernateException { + public UnsupportedOrmXsdVersionException(String requestedVersion, Origin origin) { + super( + String.format( + "Encountered unsupported orm.xml xsd version [%s] in mapping document [type=%s, name=%s]", + requestedVersion, + origin.getType(), + origin.getName() + ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLStreamConstantsUtils.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLStreamConstantsUtils.java new file mode 100644 index 0000000000..b79250f109 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLStreamConstantsUtils.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.xml; + +import javax.xml.stream.XMLStreamConstants; + +/** + * + * + * + * + * Note, copied from the uPortal project by permission of author. See + * https://github.com/Jasig/uPortal/blob/master/uportal-war/src/main/java/org/jasig/portal/xml/stream/XMLStreamConstantsUtils.java + * + * @author Eric Dalquist + */ +public class XMLStreamConstantsUtils { + /** + * Get the human readable event name for the numeric event id + */ + public static String getEventName(int eventId) { + switch (eventId) { + case XMLStreamConstants.START_ELEMENT: + return "StartElementEvent"; + case XMLStreamConstants.END_ELEMENT: + return "EndElementEvent"; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + return "ProcessingInstructionEvent"; + case XMLStreamConstants.CHARACTERS: + return "CharacterEvent"; + case XMLStreamConstants.COMMENT: + return "CommentEvent"; + case XMLStreamConstants.START_DOCUMENT: + return "StartDocumentEvent"; + case XMLStreamConstants.END_DOCUMENT: + return "EndDocumentEvent"; + case XMLStreamConstants.ENTITY_REFERENCE: + return "EntityReferenceEvent"; + case XMLStreamConstants.ATTRIBUTE: + return "AttributeBase"; + case XMLStreamConstants.DTD: + return "DTDEvent"; + case XMLStreamConstants.CDATA: + return "CDATA"; + } + return "UNKNOWN_EVENT_TYPE"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocument.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocument.java index 0822a9f6aa..3e6a1ff902 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocument.java @@ -27,6 +27,8 @@ import java.io.Serializable; import org.dom4j.Document; +import org.hibernate.jaxb.spi.Origin; + /** * Describes a parsed xml document. * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocumentImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocumentImpl.java index e936c92add..f63a7ee8aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocumentImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlDocumentImpl.java @@ -27,6 +27,9 @@ import java.io.Serializable; import org.dom4j.Document; +import org.hibernate.jaxb.spi.Origin; +import org.hibernate.jaxb.spi.SourceType; + /** * Basic implemementation of {@link XmlDocument} * @@ -36,8 +39,8 @@ public class XmlDocumentImpl implements XmlDocument, Serializable { private final Document documentTree; private final Origin origin; - public XmlDocumentImpl(Document documentTree, String originType, String originName) { - this( documentTree, new OriginImpl( originType, originName ) ); + public XmlDocumentImpl(Document documentTree, SourceType originType, String originName) { + this( documentTree, new Origin( originType, originName ) ); } public XmlDocumentImpl(Document documentTree, Origin origin) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/OriginImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlInfrastructureException.java similarity index 68% rename from hibernate-core/src/main/java/org/hibernate/internal/util/xml/OriginImpl.java rename to hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlInfrastructureException.java index 8f58fcc095..b6a3c3040c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/OriginImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XmlInfrastructureException.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -23,33 +23,19 @@ */ package org.hibernate.internal.util.xml; -import java.io.Serializable; +import org.hibernate.HibernateException; /** - * Basic implementation of {@link Origin} + * An error using XML infrastructure (jaxp, stax, etc). * * @author Steve Ebersole */ -public class OriginImpl implements Origin, Serializable { - private final String type; - private final String name; - - public OriginImpl(String type, String name) { - this.type = type; - this.name = name; +public class XmlInfrastructureException extends HibernateException { + public XmlInfrastructureException(String message) { + super( message ); } - /** - * {@inheritDoc} - */ - public String getType() { - return type; - } - - /** - * {@inheritDoc} - */ - public String getName() { - return name; + public XmlInfrastructureException(String message, Throwable root) { + super( message, root ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jaxb/internal/JaxbMappingProcessor.java b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/JaxbMappingProcessor.java index 56720bee25..d13d3e078a 100644 --- a/hibernate-core/src/main/java/org/hibernate/jaxb/internal/JaxbMappingProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/JaxbMappingProcessor.java @@ -41,16 +41,22 @@ import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; import org.jboss.logging.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; +import org.hibernate.InvalidMappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.xml.LocalXmlResourceResolver; +import org.hibernate.internal.util.xml.MappingReader; import org.hibernate.jaxb.spi.JaxbRoot; import org.hibernate.jaxb.spi.Origin; import org.hibernate.jaxb.spi.hbm.JaxbHibernateMapping; @@ -68,7 +74,7 @@ import org.hibernate.service.ServiceRegistry; public class JaxbMappingProcessor { private static final Logger log = Logger.getLogger( JaxbMappingProcessor.class ); - public static final String ASSUMED_ORM_XSD_VERSION = "2.0"; + public static final String ASSUMED_ORM_XSD_VERSION = "2.1"; public static final String VALIDATE_XML_SETTING = "hibernate.xml.validate"; public static final String HIBERNATE_MAPPING_URI = "http://www.hibernate.org/xsd/hibernate-mapping"; @@ -123,6 +129,7 @@ public class JaxbMappingProcessor { @SuppressWarnings( { "UnnecessaryLocalVariable" }) private XMLInputFactory buildStaxFactory() { XMLInputFactory staxFactory = XMLInputFactory.newInstance(); + staxFactory.setXMLResolver( LocalXmlResourceResolver.INSTANCE ); return staxFactory; } @@ -154,7 +161,23 @@ public class JaxbMappingProcessor { if ( "entity-mappings".equals( elementName ) ) { final Attribute attribute = event.asStartElement().getAttributeByName( ORM_VERSION_ATTRIBUTE_QNAME ); final String explicitVersion = attribute == null ? null : attribute.getValue(); - validationSchema = validateXml ? resolveSupportedOrmXsd( explicitVersion ) : null; + if ( !"2.1".equals( explicitVersion ) ) { + if ( validateXml ) { + MappingReader.validateMapping( + MappingReader.SupportedOrmXsdVersion.parse( explicitVersion, origin ), + staxEventReader, + origin + ); + } + staxEventReader = new LegacyJPAEventReader( + staxEventReader, + LocalXmlResourceResolver.SECOND_JPA_ORM_NS + ); + validationSchema = null; //disable JAXB validation + } + else { + validationSchema = validateXml ? resolveSupportedOrmXsd( explicitVersion, origin ) : null; + } jaxbTarget = JaxbEntityMappings.class; } else { @@ -163,7 +186,7 @@ public class JaxbMappingProcessor { log.debug( "HBM mapping document did not define namespaces; wrapping in custom event reader to introduce namespace information" ); staxEventReader = new NamespaceAddingEventReader( staxEventReader, HIBERNATE_MAPPING_URI ); } - validationSchema = validateXml ? hbmSchema() : null; + validationSchema = validateXml ? MappingReader.SupportedOrmXsdVersion.HBM_4_0.getSchema() : null; jaxbTarget = JaxbHibernateMapping.class; } @@ -177,6 +200,7 @@ public class JaxbMappingProcessor { target = unmarshaller.unmarshal( staxEventReader ); } catch ( JAXBException e ) { + e.printStackTrace(); StringBuilder builder = new StringBuilder(); builder.append( "Unable to perform unmarshalling at line number " ); builder.append( handler.getLineNumber() ); @@ -190,6 +214,7 @@ public class JaxbMappingProcessor { return new JaxbRoot( target, origin ); } + private boolean isNamespaced(StartElement startElement) { return ! "".equals( startElement.getName().getNamespaceURI() ); } @@ -206,7 +231,7 @@ public class JaxbMappingProcessor { if ( "entity-mappings".equals( rootElement.getNodeName() ) ) { final String explicitVersion = rootElement.getAttribute( "version" ); - validationSchema = validateXml ? resolveSupportedOrmXsd( explicitVersion ) : null; + validationSchema = validateXml ? resolveSupportedOrmXsd( explicitVersion, origin ) : null; jaxbTarget = JaxbEntityMappings.class; } else { @@ -228,20 +253,14 @@ public class JaxbMappingProcessor { return new JaxbRoot( target, origin ); } - private Schema resolveSupportedOrmXsd(String explicitVersion) { - final String xsdVersionString = explicitVersion == null ? ASSUMED_ORM_XSD_VERSION : explicitVersion; - if ( "1.0".equals( xsdVersionString ) ) { - return orm1Schema(); + private Schema resolveSupportedOrmXsd(String explicitVersion, Origin origin) { + if( StringHelper.isEmpty(explicitVersion)){ + return MappingReader.SupportedOrmXsdVersion.ORM_2_1.getSchema(); } - else if ( "2.0".equals( xsdVersionString ) ) { - return orm2Schema(); - } - throw new IllegalArgumentException( "Unsupported orm.xml XSD version encountered [" + xsdVersionString + "]" ); + return MappingReader.SupportedOrmXsdVersion.parse( explicitVersion, origin ).getSchema(); } public static final String HBM_SCHEMA_NAME = "org/hibernate/hibernate-mapping-4.0.xsd"; - public static final String ORM_1_SCHEMA_NAME = "org/hibernate/ejb/orm_1_0.xsd"; - public static final String ORM_2_SCHEMA_NAME = "org/hibernate/ejb/orm_2_0.xsd"; private Schema hbmSchema; @@ -252,23 +271,6 @@ public class JaxbMappingProcessor { return hbmSchema; } - private Schema orm1Schema; - - private Schema orm1Schema() { - if ( orm1Schema == null ) { - orm1Schema = resolveLocalSchema( ORM_1_SCHEMA_NAME ); - } - return orm1Schema; - } - - private Schema orm2Schema; - - private Schema orm2Schema() { - if ( orm2Schema == null ) { - orm2Schema = resolveLocalSchema( ORM_2_SCHEMA_NAME ); - } - return orm2Schema; - } private Schema resolveLocalSchema(String schemaName) { return resolveLocalSchema( schemaName, XMLConstants.W3C_XML_SCHEMA_NS_URI ); diff --git a/hibernate-core/src/main/java/org/hibernate/jaxb/internal/LegacyJPAEventReader.java b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/LegacyJPAEventReader.java new file mode 100644 index 0000000000..2678e9add6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/LegacyJPAEventReader.java @@ -0,0 +1,89 @@ +package org.hibernate.jaxb.internal; + +import java.util.ArrayList; +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.Namespace; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.EventReaderDelegate; + +import org.hibernate.internal.util.xml.LocalXmlResourceResolver; + +/** + * @author Strong Liu + */ +public class LegacyJPAEventReader extends EventReaderDelegate { + private final XMLEventFactory xmlEventFactory; + private final String namespaceUri; + + public LegacyJPAEventReader(XMLEventReader reader, String namespaceUri) { + this( reader, XMLEventFactory.newInstance(), namespaceUri ); + } + + public LegacyJPAEventReader(XMLEventReader reader, XMLEventFactory xmlEventFactory, String namespaceUri) { + super( reader ); + this.xmlEventFactory = xmlEventFactory; + this.namespaceUri = namespaceUri; + } + + private StartElement withNamespace(StartElement startElement) { + // otherwise, wrap the start element event to provide a default namespace mapping + final List namespaces = new ArrayList(); + namespaces.add( xmlEventFactory.createNamespace( "", namespaceUri ) ); + Iterator originalNamespaces = startElement.getNamespaces(); + while ( originalNamespaces.hasNext() ) { + Namespace ns = (Namespace) originalNamespaces.next(); + if ( !LocalXmlResourceResolver.INITIAL_JPA_ORM_NS.equals( ns.getNamespaceURI() ) ) { + namespaces.add( ns ); + } + } + Iterator attributes; + if ( "entity-mappings".equals( startElement.getName().getLocalPart() ) ) { + List st = new ArrayList(); + Iterator itr = startElement.getAttributes(); + while ( itr.hasNext() ) { + Attribute obj = (Attribute) itr.next(); + if ( "version".equals( obj.getName().getLocalPart() ) ) { + if ( "".equals( obj.getName().getPrefix() ) ) { + st.add( xmlEventFactory.createAttribute( obj.getName(), "2.1" ) ); + } + } + else { + st.add( obj ); + } + } + attributes = st.iterator(); + } else { + attributes = startElement.getAttributes(); + } + + return xmlEventFactory.createStartElement( + new QName( namespaceUri, startElement.getName().getLocalPart() ), + attributes, + namespaces.iterator() + ); + } + + @Override + public XMLEvent nextEvent() throws XMLStreamException { + return wrap( super.nextEvent() ); + } + + private XMLEvent wrap(XMLEvent event) { + if ( event.isStartElement() ) { + return withNamespace( event.asStartElement() ); + } + return event; + } + + @Override + public XMLEvent peek() throws XMLStreamException { + return wrap( super.peek() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jaxb/internal/NamespaceAddingEventReader.java b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/NamespaceAddingEventReader.java index 3407379c98..eb21651478 100644 --- a/hibernate-core/src/main/java/org/hibernate/jaxb/internal/NamespaceAddingEventReader.java +++ b/hibernate-core/src/main/java/org/hibernate/jaxb/internal/NamespaceAddingEventReader.java @@ -35,6 +35,8 @@ import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import javax.xml.stream.util.EventReaderDelegate; +import org.hibernate.internal.util.xml.LocalXmlResourceResolver; + /** * Used to wrap a StAX {@link XMLEventReader} in order to introduce namespaces into the underlying document. This * is intended for temporary migration feature to allow legacy HBM mapping documents (DTD-based) to continue to @@ -62,7 +64,10 @@ public class NamespaceAddingEventReader extends EventReaderDelegate { namespaces.add( xmlEventFactory.createNamespace( "", namespaceUri ) ); Iterator originalNamespaces = startElement.getNamespaces(); while ( originalNamespaces.hasNext() ) { - namespaces.add( (Namespace) originalNamespaces.next() ); + Namespace ns = (Namespace) originalNamespaces.next(); + if ( !LocalXmlResourceResolver.INITIAL_JPA_ORM_NS.equals( ns.getNamespaceURI() ) ) { + namespaces.add( ns ); + } } return xmlEventFactory.createStartElement( new QName( namespaceUri, startElement.getName().getLocalPart() ), @@ -73,7 +78,10 @@ public class NamespaceAddingEventReader extends EventReaderDelegate { @Override public XMLEvent nextEvent() throws XMLStreamException { - XMLEvent event = super.nextEvent(); + return wrap( super.nextEvent() ); + } + + private XMLEvent wrap(XMLEvent event) { if ( event.isStartElement() ) { return withNamespace( event.asStartElement() ); } @@ -82,12 +90,6 @@ public class NamespaceAddingEventReader extends EventReaderDelegate { @Override public XMLEvent peek() throws XMLStreamException { - XMLEvent event = super.peek(); - if ( event.isStartElement() ) { - return withNamespace( event.asStartElement() ); - } - else { - return event; - } + return wrap( super.peek() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index dcc56e732d..7e496c34ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -212,7 +212,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { } public void setRole(String role) { - this.role = role==null ? null : role.intern(); + this.role = role; } public void setSorted(boolean sorted) { @@ -549,7 +549,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { } public void setLoaderName(String name) { - this.loaderName = name==null ? null : name.intern(); + this.loaderName = name; } public String getReferencedPropertyName() { @@ -557,7 +557,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { } public void setReferencedPropertyName(String propertyRef) { - this.referencedPropertyName = propertyRef==null ? null : propertyRef.intern(); + this.referencedPropertyName = propertyRef; } public boolean isOptimisticLocked() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index ee638cde90..a032d22029 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -36,6 +36,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; +import org.hibernate.internal.util.StringHelper; import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata; @@ -395,8 +396,6 @@ public class Table implements RelationalModel, Serializable { Iterator iter = getColumnIterator(); List results = new ArrayList(); - int uniqueIndexInteger = 0; - while ( iter.hasNext() ) { Column column = (Column) iter.next(); @@ -423,9 +422,8 @@ public class Table implements RelationalModel, Serializable { } if ( column.isUnique() ) { - uniqueIndexInteger++; UniqueKey uk = getOrCreateUniqueKey( - "UK_" + name + "_" + uniqueIndexInteger); + StringHelper.randomFixedLengthHex("UK_")); uk.addColumn( column ); alter.append( dialect.getUniqueDelegate() .applyUniqueToColumn( column ) ); @@ -494,7 +492,6 @@ public class Table implements RelationalModel, Serializable { } Iterator iter = getColumnIterator(); - int uniqueIndexInteger = 0; while ( iter.hasNext() ) { Column col = (Column) iter.next(); @@ -528,9 +525,8 @@ public class Table implements RelationalModel, Serializable { } if ( col.isUnique() ) { - uniqueIndexInteger++; UniqueKey uk = getOrCreateUniqueKey( - "uc_" + name + "_" + uniqueIndexInteger); + StringHelper.randomFixedLengthHex("UK_")); uk.addColumn( col ); buf.append( dialect.getUniqueDelegate() .applyUniqueToColumn( col ) ); @@ -653,7 +649,7 @@ public class Table implements RelationalModel, Serializable { } public UniqueKey createUniqueKey(List keyColumns) { - String keyName = "UK_" + uniqueColumnString( keyColumns.iterator() ); + String keyName = StringHelper.randomFixedLengthHex("UK_"); UniqueKey uk = getOrCreateUniqueKey( keyName ); uk.addColumns( keyColumns.iterator() ); return uk; @@ -693,9 +689,7 @@ public class Table implements RelationalModel, Serializable { fk.setName( keyName ); } else { - fk.setName( "FK" + uniqueColumnString( keyColumns.iterator(), referencedEntityName ) ); - //TODO: add referencedClass to disambiguate to FKs on the same - // columns, pointing to different tables + fk.setName( StringHelper.randomFixedLengthHex("FK_") ); } fk.setTable( this ); foreignKeys.put( key, fk ); @@ -714,22 +708,6 @@ public class Table implements RelationalModel, Serializable { } - public String uniqueColumnString(Iterator iterator) { - return uniqueColumnString( iterator, null ); - } - - public String uniqueColumnString(Iterator iterator, String referencedEntityName) { - int result = 0; - if ( referencedEntityName != null ) { - result += referencedEntityName.hashCode(); - } - while ( iterator.hasNext() ) { - result += iterator.next().hashCode(); - } - return ( Integer.toHexString( name.hashCode() ) + Integer.toHexString( result ) ).toUpperCase(); - } - - public String getSchema() { return schema; diff --git a/hibernate-core/src/main/resources/org/hibernate/ejb/orm_1_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd similarity index 97% rename from hibernate-core/src/main/resources/org/hibernate/ejb/orm_1_0.xsd rename to hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd index c21e996c4a..55d3517623 100644 --- a/hibernate-core/src/main/resources/org/hibernate/ejb/orm_1_0.xsd +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_1_0.xsd @@ -1,1540 +1,1540 @@ - - - - - - - - - @(#)orm_1_0.xsd 1.0 Feb 14 2006 - - - - - - - - - - - - - - - - - - - - - - The entity-mappings element is the root element of an mapping - file. It contains the following four types of elements: - - 1. The persistence-unit-metadata element contains metadata - for the entire persistence unit. It is undefined if this element - occurs in multiple mapping files within the same persistence unit. - - 2. The package, schema, catalog and access elements apply to all of - the entity, mapped-superclass and embeddable elements defined in - the same file in which they occur. - - 3. The sequence-generator, table-generator, named-query, - named-native-query and sql-result-set-mapping elements are global - to the persistence unit. It is undefined to have more than one - sequence-generator or table-generator of the same name in the same - or different mapping files in a persistence unit. It is also - undefined to have more than one named-query or named-native-query - of the same name in the same or different mapping files in a - persistence unit. - - 4. The entity, mapped-superclass and embeddable elements each define - the mapping information for a managed persistent class. The mapping - information contained in these elements may be complete or it may - be partial. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Metadata that applies to the persistence unit and not just to - the mapping file in which it is contained. - - If the xml-mapping-metadata-complete element is specified then - the complete set of mapping metadata for the persistence unit - is contained in the XML mapping files for the persistence unit. - - - - - - - - - - - - - - - - These defaults are applied to the persistence unit as a whole - unless they are overridden by local annotation or XML - element settings. - - schema - Used as the schema for all tables or secondary tables - that apply to the persistence unit - catalog - Used as the catalog for all tables or secondary tables - that apply to the persistence unit - access - Used as the access type for all managed classes in - the persistence unit - cascade-persist - Adds cascade-persist to the set of cascade options - in entity relationships of the persistence unit - entity-listeners - List of default entity listeners to be invoked - on each entity in the persistence unit. - - - - - - - - - - - - - - - - - - - Defines the settings and mappings for an entity. Is allowed to be - sparsely populated and used in conjunction with the annotations. - Alternatively, the metadata-complete attribute can be used to - indicate that no annotations on the entity class (and its fields - or properties) are to be processed. If this is the case then - the defaulting rules for the entity and its subelements will - be recursively applied. - - @Target(TYPE) @Retention(RUNTIME) - public @interface Entity { - String name() default ""; - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This element contains the entity field or property mappings. - It may be sparsely populated to include only a subset of the - fields or properties. If metadata-complete for the entity is true - then the remainder of the attributes will be defaulted according - to the default rules. - - - - - - - - - - - - - - - - - - - - - - - - - - This element determines how the persistence provider accesses the - state of an entity or embedded object. - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface EntityListeners { - Class[] value(); - } - - - - - - - - - - - - - - - Defines an entity listener to be invoked at lifecycle events - for the entities that list this listener. - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PrePersist {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PostPersist {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PreRemove {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PostRemove {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PreUpdate {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PostUpdate {} - - - - - - - - - - - - - @Target({METHOD}) @Retention(RUNTIME) - public @interface PostLoad {} - - - - - - - - - - - - - @Target({}) @Retention(RUNTIME) - public @interface QueryHint { - String name(); - String value(); - } - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface NamedQuery { - String name(); - String query(); - QueryHint[] hints() default {}; - } - - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface NamedNativeQuery { - String name(); - String query(); - QueryHint[] hints() default {}; - Class resultClass() default void.class; - String resultSetMapping() default ""; //named SqlResultSetMapping - } - - - - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface SqlResultSetMapping { - String name(); - EntityResult[] entities() default {}; - ColumnResult[] columns() default {}; - } - - - - - - - - - - - - - - - - - @Target({}) @Retention(RUNTIME) - public @interface EntityResult { - Class entityClass(); - FieldResult[] fields() default {}; - String discriminatorColumn() default ""; - } - - - - - - - - - - - - - - - - - @Target({}) @Retention(RUNTIME) - public @interface FieldResult { - String name(); - String column(); - } - - - - - - - - - - - - - - @Target({}) @Retention(RUNTIME) - public @interface ColumnResult { - String name(); - } - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface Table { - String name() default ""; - String catalog() default ""; - String schema() default ""; - UniqueConstraint[] uniqueConstraints() default {}; - } - - - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface SecondaryTable { - String name(); - String catalog() default ""; - String schema() default ""; - PrimaryKeyJoinColumn[] pkJoinColumns() default {}; - UniqueConstraint[] uniqueConstraints() default {}; - } - - - - - - - - - - - - - - - - - - - @Target({}) @Retention(RUNTIME) - public @interface UniqueConstraint { - String[] columnNames(); - } - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Column { - String name() default ""; - boolean unique() default false; - boolean nullable() default true; - boolean insertable() default true; - boolean updatable() default true; - String columnDefinition() default ""; - String table() default ""; - int length() default 255; - int precision() default 0; // decimal precision - int scale() default 0; // decimal scale - } - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface JoinColumn { - String name() default ""; - String referencedColumnName() default ""; - boolean unique() default false; - boolean nullable() default true; - boolean insertable() default true; - boolean updatable() default true; - String columnDefinition() default ""; - String table() default ""; - } - - - - - - - - - - - - - - - - - - - - public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; - - - - - - - - - - - - - - - - - - @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) - public @interface AttributeOverride { - String name(); - Column column(); - } - - - - - - - - - - - - - - - - @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) - public @interface AssociationOverride { - String name(); - JoinColumn[] joinColumns(); - } - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface IdClass { - Class value(); - } - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Id {} - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface EmbeddedId {} - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Transient {} - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Version {} - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Basic { - FetchType fetch() default EAGER; - boolean optional() default true; - } - - - - - - - - - - - - - - - - - - - - - - - public enum FetchType { LAZY, EAGER }; - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Lob {} - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Temporal { - TemporalType value(); - } - - - - - - - - - - - - - public enum TemporalType { - DATE, // java.sql.Date - TIME, // java.sql.Time - TIMESTAMP // java.sql.Timestamp - } - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Enumerated { - EnumType value() default ORDINAL; - } - - - - - - - - - - - - - public enum EnumType { - ORDINAL, - STRING - } - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface ManyToOne { - Class targetEntity() default void.class; - CascadeType[] cascade() default {}; - FetchType fetch() default EAGER; - boolean optional() default true; - } - - - - - - - - - - - - - - - - - - - - - - - public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH}; - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface OneToOne { - Class targetEntity() default void.class; - CascadeType[] cascade() default {}; - FetchType fetch() default EAGER; - boolean optional() default true; - String mappedBy() default ""; - } - - - - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface OneToMany { - Class targetEntity() default void.class; - CascadeType[] cascade() default {}; - FetchType fetch() default LAZY; - String mappedBy() default ""; - } - - - - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface JoinTable { - String name() default ""; - String catalog() default ""; - String schema() default ""; - JoinColumn[] joinColumns() default {}; - JoinColumn[] inverseJoinColumns() default {}; - UniqueConstraint[] uniqueConstraints() default {}; - } - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface ManyToMany { - Class targetEntity() default void.class; - CascadeType[] cascade() default {}; - FetchType fetch() default LAZY; - String mappedBy() default ""; - } - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface GeneratedValue { - GenerationType strategy() default AUTO; - String generator() default ""; - } - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface MapKey { - String name() default ""; - } - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface OrderBy { - String value() default ""; - } - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface Inheritance { - InheritanceType strategy() default SINGLE_TABLE; - } - - - - - - - - - - - - - public enum InheritanceType - { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; - - - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface DiscriminatorValue { - String value(); - } - - - - - - - - - - - - - public enum DiscriminatorType { STRING, CHAR, INTEGER }; - - - - - - - - - - - - - - - - - @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) - public @interface PrimaryKeyJoinColumn { - String name() default ""; - String referencedColumnName() default ""; - String columnDefinition() default ""; - } - - - - - - - - - - - - - - - @Target({TYPE}) @Retention(RUNTIME) - public @interface DiscriminatorColumn { - String name() default "DTYPE"; - DiscriminatorType discriminatorType() default STRING; - String columnDefinition() default ""; - int length() default 31; - } - - - - - - - - - - - - - - - - Defines the settings and mappings for embeddable objects. Is - allowed to be sparsely populated and used in conjunction with - the annotations. Alternatively, the metadata-complete attribute - can be used to indicate that no annotations are to be processed - in the class. If this is the case then the defaulting rules will - be recursively applied. - - @Target({TYPE}) @Retention(RUNTIME) - public @interface Embeddable {} - - - - - - - - - - - - - - - - - - - - - - - - - - - - @Target({METHOD, FIELD}) @Retention(RUNTIME) - public @interface Embedded {} - - - - - - - - - - - - - - - - Defines the settings and mappings for a mapped superclass. Is - allowed to be sparsely populated and used in conjunction with - the annotations. Alternatively, the metadata-complete attribute - can be used to indicate that no annotations are to be processed - If this is the case then the defaulting rules will be recursively - applied. - - @Target(TYPE) @Retention(RUNTIME) - public @interface MappedSuperclass{} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) - public @interface SequenceGenerator { - String name(); - String sequenceName() default ""; - int initialValue() default 1; - int allocationSize() default 50; - } - - - - - - - - - - - - - - - - @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) - public @interface TableGenerator { - String name(); - String table() default ""; - String catalog() default ""; - String schema() default ""; - String pkColumnName() default ""; - String valueColumnName() default ""; - String pkColumnValue() default ""; - int initialValue() default 0; - int allocationSize() default 50; - UniqueConstraint[] uniqueConstraints() default {}; - } - - - - - - - - - - - - - - - - - - - - + + + + + + + + + @(#)orm_1_0.xsd 1.0 Feb 14 2006 + + + + + + + + + + + + + + + + + + + + + + The entity-mappings element is the root element of an mapping + file. It contains the following four types of elements: + + 1. The persistence-unit-metadata element contains metadata + for the entire persistence unit. It is undefined if this element + occurs in multiple mapping files within the same persistence unit. + + 2. The package, schema, catalog and access elements apply to all of + the entity, mapped-superclass and embeddable elements defined in + the same file in which they occur. + + 3. The sequence-generator, table-generator, named-query, + named-native-query and sql-result-set-mapping elements are global + to the persistence unit. It is undefined to have more than one + sequence-generator or table-generator of the same name in the same + or different mapping files in a persistence unit. It is also + undefined to have more than one named-query or named-native-query + of the same name in the same or different mapping files in a + persistence unit. + + 4. The entity, mapped-superclass and embeddable elements each define + the mapping information for a managed persistent class. The mapping + information contained in these elements may be complete or it may + be partial. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata that applies to the persistence unit and not just to + the mapping file in which it is contained. + + If the xml-mapping-metadata-complete element is specified then + the complete set of mapping metadata for the persistence unit + is contained in the XML mapping files for the persistence unit. + + + + + + + + + + + + + + + + These defaults are applied to the persistence unit as a whole + unless they are overridden by local annotation or XML + element settings. + + schema - Used as the schema for all tables or secondary tables + that apply to the persistence unit + catalog - Used as the catalog for all tables or secondary tables + that apply to the persistence unit + access - Used as the access type for all managed classes in + the persistence unit + cascade-persist - Adds cascade-persist to the set of cascade options + in entity relationships of the persistence unit + entity-listeners - List of default entity listeners to be invoked + on each entity in the persistence unit. + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for an entity. Is allowed to be + sparsely populated and used in conjunction with the annotations. + Alternatively, the metadata-complete attribute can be used to + indicate that no annotations on the entity class (and its fields + or properties) are to be processed. If this is the case then + the defaulting rules for the entity and its subelements will + be recursively applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface Entity { + String name() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element contains the entity field or property mappings. + It may be sparsely populated to include only a subset of the + fields or properties. If metadata-complete for the entity is true + then the remainder of the attributes will be defaulted according + to the default rules. + + + + + + + + + + + + + + + + + + + + + + + + + + This element determines how the persistence provider accesses the + state of an entity or embedded object. + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface EntityListeners { + Class[] value(); + } + + + + + + + + + + + + + + + Defines an entity listener to be invoked at lifecycle events + for the entities that list this listener. + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PrePersist {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostPersist {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreRemove {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostRemove {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreUpdate {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostUpdate {} + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostLoad {} + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface QueryHint { + String name(); + String value(); + } + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedNativeQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + Class resultClass() default void.class; + String resultSetMapping() default ""; //named SqlResultSetMapping + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SqlResultSetMapping { + String name(); + EntityResult[] entities() default {}; + ColumnResult[] columns() default {}; + } + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface EntityResult { + Class entityClass(); + FieldResult[] fields() default {}; + String discriminatorColumn() default ""; + } + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface FieldResult { + String name(); + String column(); + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ColumnResult { + String name(); + } + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Table { + String name() default ""; + String catalog() default ""; + String schema() default ""; + UniqueConstraint[] uniqueConstraints() default {}; + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SecondaryTable { + String name(); + String catalog() default ""; + String schema() default ""; + PrimaryKeyJoinColumn[] pkJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface UniqueConstraint { + String[] columnNames(); + } + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Column { + String name() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + } + + + + + + + + + + + + + + + + + + + + public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AttributeOverride { + String name(); + Column column(); + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AssociationOverride { + String name(); + JoinColumn[] joinColumns(); + } + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface IdClass { + Class value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Id {} + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface EmbeddedId {} + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Transient {} + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Version {} + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Basic { + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + public enum FetchType { LAZY, EAGER }; + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Lob {} + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Temporal { + TemporalType value(); + } + + + + + + + + + + + + + public enum TemporalType { + DATE, // java.sql.Date + TIME, // java.sql.Time + TIMESTAMP // java.sql.Timestamp + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Enumerated { + EnumType value() default ORDINAL; + } + + + + + + + + + + + + + public enum EnumType { + ORDINAL, + STRING + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH}; + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + JoinColumn[] inverseJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + } + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface GeneratedValue { + GenerationType strategy() default AUTO; + String generator() default ""; + } + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKey { + String name() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderBy { + String value() default ""; + } + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Inheritance { + InheritanceType strategy() default SINGLE_TABLE; + } + + + + + + + + + + + + + public enum InheritanceType + { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorValue { + String value(); + } + + + + + + + + + + + + + public enum DiscriminatorType { STRING, CHAR, INTEGER }; + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface PrimaryKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorColumn { + String name() default "DTYPE"; + DiscriminatorType discriminatorType() default STRING; + String columnDefinition() default ""; + int length() default 31; + } + + + + + + + + + + + + + + + + Defines the settings and mappings for embeddable objects. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + in the class. If this is the case then the defaulting rules will + be recursively applied. + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Embeddable {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Embedded {} + + + + + + + + + + + + + + + + Defines the settings and mappings for a mapped superclass. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + If this is the case then the defaulting rules will be recursively + applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface MappedSuperclass{} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface SequenceGenerator { + String name(); + String sequenceName() default ""; + int initialValue() default 1; + int allocationSize() default 50; + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface TableGenerator { + String name(); + String table() default ""; + String catalog() default ""; + String schema() default ""; + String pkColumnName() default ""; + String valueColumnName() default ""; + String pkColumnValue() default ""; + int initialValue() default 0; + int allocationSize() default 50; + UniqueConstraint[] uniqueConstraints() default {}; + } + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/main/resources/org/hibernate/ejb/orm_2_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_0.xsd similarity index 100% rename from hibernate-core/src/main/resources/org/hibernate/ejb/orm_2_0.xsd rename to hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_0.xsd diff --git a/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/orm_2_1.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_1.xsd similarity index 100% rename from hibernate-entitymanager/src/main/resources/org/hibernate/jpa/orm_2_1.xsd rename to hibernate-core/src/main/resources/org/hibernate/jpa/orm_2_1.xsd diff --git a/hibernate-core/src/main/xjb/orm-bindings.xjb b/hibernate-core/src/main/xjb/orm-bindings.xjb index 1ed292a425..c25db84fd5 100644 --- a/hibernate-core/src/main/xjb/orm-bindings.xjb +++ b/hibernate-core/src/main/xjb/orm-bindings.xjb @@ -6,7 +6,7 @@ jaxb:extensionBindingPrefixes="inheritance" version="2.1"> - + diff --git a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java index 15a1dd151c..54c94f6e27 100644 --- a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java +++ b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java @@ -30,6 +30,7 @@ import org.hibernate.metamodel.MetadataSources; import org.hibernate.metamodel.internal.MetadataImpl; import org.hibernate.metamodel.spi.binding.EntityBinding; import org.hibernate.metamodel.spi.source.MappingException; +import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.junit4.BaseUnitTestCase; import static junit.framework.Assert.assertNotNull; @@ -58,6 +59,7 @@ public class OrmXmlParserTests extends BaseUnitTestCase { } @Test(expected = MappingException.class) + @FailureExpectedWithNewMetamodel(message = "JAXB validation is disabled ATM to support both JPA 2.1 and previous orm.xml") public void testInvalidOrmXmlThrowsException() { MetadataSources sources = new MetadataSources( new StandardServiceRegistryBuilder().build() ); sources.addResource( "org/hibernate/metamodel/internal/source/annotations/xml/orm-invalid.xml" ); diff --git a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/mocker/AbstractMockerTest.java b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/mocker/AbstractMockerTest.java index b182c422b5..236cf8fa06 100644 --- a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/mocker/AbstractMockerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/mocker/AbstractMockerTest.java @@ -39,6 +39,10 @@ import org.jboss.jandex.Indexer; import org.hibernate.AnnotationException; import org.hibernate.HibernateException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.jaxb.internal.JaxbMappingProcessor; +import org.hibernate.jaxb.spi.JaxbRoot; +import org.hibernate.jaxb.spi.Origin; +import org.hibernate.jaxb.spi.SourceType; import org.hibernate.jaxb.spi.orm.JaxbEntityMappings; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; @@ -50,8 +54,9 @@ import static org.junit.Assert.fail; * @author Strong Liu */ public abstract class AbstractMockerTest { - private static final String ORM1_MAPPING_XSD = "org/hibernate/ejb/orm_1_0.xsd"; - private static final String ORM2_MAPPING_XSD = "org/hibernate/ejb/orm_2_0.xsd"; + private static final String ORM1_MAPPING_XSD = "org/hibernate/jpa/orm_1_0.xsd"; + private static final String ORM2_MAPPING_XSD = "org/hibernate/jpa/orm_2_0.xsd"; + private static final String ORM2_1_MAPPING_XSD = "org/hibernate/jpa/orm_2_1.xsd"; private IndexBuilder indexBuilder; private Index index; private ServiceRegistry serviceRegistry; @@ -69,23 +74,14 @@ public abstract class AbstractMockerTest { ClassLoaderService classLoaderService = getServiceRegistry().getService( ClassLoaderService.class ); List xmlEntityMappingsList = new ArrayList(); for ( String fileName : mappingFiles ) { - JaxbEntityMappings entityMappings; - try { - entityMappings = XmlHelper.unmarshallXml( - packagePrefix + fileName, ORM2_MAPPING_XSD, JaxbEntityMappings.class, classLoaderService - ).getRoot(); - } - catch ( JAXBException orm2Exception ) { - // if we cannot parse against orm_2_0.xsd we try orm_1_0.xsd for backwards compatibility - try { - entityMappings = XmlHelper.unmarshallXml( - packagePrefix + fileName, ORM1_MAPPING_XSD, JaxbEntityMappings.class, classLoaderService - ).getRoot(); - } - catch ( JAXBException orm1Exception ) { - throw new AnnotationException( "Unable to parse xml configuration.", orm1Exception ); - } - } + JaxbMappingProcessor processor = new JaxbMappingProcessor( getServiceRegistry() ); + JaxbRoot jaxbRoot = processor.unmarshal( + classLoaderService.locateResourceStream( packagePrefix + fileName ), + new Origin( SourceType.FILE, packagePrefix + fileName ) + ); + JaxbEntityMappings entityMappings = (JaxbEntityMappings)jaxbRoot.getRoot(); + + xmlEntityMappingsList.add( entityMappings ); } return new EntityMappingsMocker( xmlEntityMappingsList, getIndex(), getServiceRegistry() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/AbstractJPAIndexTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/AbstractJPAIndexTest.java new file mode 100644 index 0000000000..b60a5665f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/AbstractJPAIndexTest.java @@ -0,0 +1,143 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013 by Red Hat Inc and/or its affiliates or by + * third-party contributors as indicated by either @author tags or express + * copyright attribution statements applied by the authors. All + * third-party contributions are distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.index.jpa; + +import java.util.Iterator; + +import org.junit.Test; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.Bag; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Index; +import org.hibernate.mapping.Join; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Set; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Strong Liu + */ + +public abstract class AbstractJPAIndexTest extends BaseCoreFunctionalTestCase { + @Test + public void testTableIndex() { + PersistentClass entity = configuration().getClassMapping( Car.class.getName() ); + Iterator itr = entity.getTable().getUniqueKeyIterator(); + assertTrue( itr.hasNext() ); + UniqueKey uk = (UniqueKey) itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( StringHelper.isNotEmpty( uk.getName() ) ); + assertEquals( 2, uk.getColumnSpan() ); + Column column = (Column) uk.getColumns().get( 0 ); + assertEquals( "brand", column.getName() ); + column = (Column) uk.getColumns().get( 1 ); + assertEquals( "producer", column.getName() ); + assertSame( entity.getTable(), uk.getTable() ); + + + itr = entity.getTable().getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = (Index)itr.next(); + assertFalse( itr.hasNext() ); + assertEquals( "Car_idx", index.getName() ); + assertEquals( 1, index.getColumnSpan() ); + column = index.getColumnIterator().next(); + assertEquals( "since", column.getName() ); + assertSame( entity.getTable(), index.getTable() ); + } + + @Test + public void testSecondaryTableIndex(){ + PersistentClass entity = configuration().getClassMapping( Car.class.getName() ); + + Join join = (Join)entity.getJoinIterator().next(); + Iterator itr = join.getTable().getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 2, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "dealer_name", column.getName() ); + column = columnIterator.next(); + assertEquals( "rate", column.getName() ); + assertSame( join.getTable(), index.getTable() ); + + } + + @Test + public void testCollectionTableIndex(){ + PersistentClass entity = configuration().getClassMapping( Car.class.getName() ); + Property property = entity.getProperty( "otherDealers" ); + Set set = (Set)property.getValue(); + Table collectionTable = set.getCollectionTable(); + + Iterator itr = collectionTable.getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 1, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "name", column.getName() ); + assertSame( collectionTable, index.getTable() ); + + } + + @Test + public void testJoinTableIndex(){ + PersistentClass entity = configuration().getClassMapping( Importer.class.getName() ); + Property property = entity.getProperty( "cars" ); + Bag set = (Bag)property.getValue(); + Table collectionTable = set.getCollectionTable(); + + Iterator itr = collectionTable.getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 1, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "importers_id", column.getName() ); + assertSame( collectionTable, index.getTable() ); + } + +// @Test +// public void testTableGeneratorIndex(){ +// //todo +// } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java index a0bea61957..d67522af78 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java @@ -23,9 +23,20 @@ */ package org.hibernate.test.annotations.index.jpa; +import java.util.List; +import java.util.Set; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Index; +import javax.persistence.ManyToMany; +import javax.persistence.SecondaryTable; import javax.persistence.Table; @@ -33,13 +44,85 @@ import javax.persistence.Table; * @author Strong Liu */ @Entity -@Table( indexes = {@Index( unique = true, columnList = "brand, producer") -, @Index( name = "Car_idx", columnList = "since DESC")}) +@Table(indexes = { + @Index(unique = true, columnList = "brand, producer") + , @Index(name = "Car_idx", columnList = "since DESC") +}) +@SecondaryTable(name = "T_DEALER", indexes = @Index(columnList = "dealer_name ASC, rate DESC")) public class Car { @Id - long id; - String brand; - String producer; - long since; + private long id; + private String brand; + private String producer; + private long since; + @AttributeOverrides({ + @AttributeOverride(name = "name", column = @Column(name = "dealer_name", table = "T_DEALER")), + @AttributeOverride(name = "rate", column = @Column(table = "T_DEALER")) + }) + @Embedded + private Dealer dealer; + @ElementCollection + @CollectionTable(name = "CAR_DEALTERS", indexes = @Index(columnList = "name")) + private Set otherDealers; + + + @ManyToMany(cascade = CascadeType.ALL, mappedBy = "cars") + private List importers; + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public Dealer getDealer() { + return dealer; + } + + public void setDealer(Dealer dealer) { + this.dealer = dealer; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public List getImporters() { + return importers; + } + + public void setImporters(List importers) { + this.importers = importers; + } + + public Set getOtherDealers() { + return otherDealers; + } + + public void setOtherDealers(Set otherDealers) { + this.otherDealers = otherDealers; + } + + public String getProducer() { + return producer; + } + + public void setProducer(String producer) { + this.producer = producer; + } + + public long getSince() { + return since; + } + + public void setSince(long since) { + this.since = since; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java index 3c01d5f5f1..309bf19a3b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java @@ -23,11 +23,30 @@ */ package org.hibernate.test.annotations.index.jpa; -import javax.persistence.Entity; +import java.io.Serializable; +import javax.persistence.Embeddable; /** * @author Strong Liu */ -@Entity -public class Dealer { +@Embeddable +public class Dealer implements Serializable { + private String name; + private long rate; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getRate() { + return rate; + } + + public void setRate(long rate) { + this.rate = rate; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Importer.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Importer.java new file mode 100644 index 0000000000..378ec7ecc1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Importer.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013 by Red Hat Inc and/or its affiliates or by + * third-party contributors as indicated by either @author tags or express + * copyright attribution statements applied by the authors. All + * third-party contributions are distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.index.jpa; + +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +/** + * @author Strong Liu + */ +@Entity +public class Importer { + @Id + private long id; + + private String name; + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable( name = "CAR_IMPORTER",indexes = @Index(columnList = "importers_id")) + private List cars; + + public List getCars() { + return cars; + } + + public void setCars(List cars) { + this.cars = cars; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java index 297f21e08c..845ab495bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java @@ -23,144 +23,21 @@ */ package org.hibernate.test.annotations.index.jpa; -import java.util.Iterator; -import org.junit.Test; - -import org.hibernate.internal.util.StringHelper; -import org.hibernate.mapping.Bag; -import org.hibernate.mapping.Column; -import org.hibernate.mapping.Index; -import org.hibernate.mapping.Join; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Property; -import org.hibernate.mapping.Set; -import org.hibernate.mapping.Table; -import org.hibernate.mapping.UniqueKey; -import org.hibernate.test.annotations.embedded.Address; -import org.hibernate.test.annotations.embedded.AddressType; -import org.hibernate.test.annotations.embedded.Book; -import org.hibernate.test.annotations.embedded.Person; -import org.hibernate.test.annotations.embedded.Summary; -import org.hibernate.test.annotations.embedded.WealthyPerson; -import org.hibernate.test.event.collection.detached.Alias; import org.hibernate.testing.FailureExpectedWithNewMetamodel; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; /** * @author Strong Liu */ @FailureExpectedWithNewMetamodel -public class IndexTest extends BaseCoreFunctionalTestCase { +public class IndexTest extends AbstractJPAIndexTest { @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Car.class, - Book.class, - Summary.class, - WealthyPerson.class, - Person.class, - AddressType.class, - Address.class, - Alias.class, - org.hibernate.test.event.collection.detached.Character.class + return new Class[] { + Car.class, + Dealer.class, + Importer.class }; } - @Test - public void testTableIndex() { - PersistentClass entity = configuration().getClassMapping( Car.class.getName() ); - Iterator itr = entity.getTable().getUniqueKeyIterator(); - assertTrue( itr.hasNext() ); - UniqueKey uk = (UniqueKey) itr.next(); - assertFalse( itr.hasNext() ); - assertTrue( StringHelper.isNotEmpty( uk.getName() ) ); - assertEquals( 2, uk.getColumnSpan() ); - Column column = (Column) uk.getColumns().get( 0 ); - assertEquals( "brand", column.getName() ); - column = (Column) uk.getColumns().get( 1 ); - assertEquals( "producer", column.getName() ); - assertSame( entity.getTable(), uk.getTable() ); - - - itr = entity.getTable().getIndexIterator(); - assertTrue( itr.hasNext() ); - Index index = (Index)itr.next(); - assertFalse( itr.hasNext() ); - assertEquals( "Car_idx", index.getName() ); - assertEquals( 1, index.getColumnSpan() ); - column = index.getColumnIterator().next(); - assertEquals( "since", column.getName() ); - assertSame( entity.getTable(), index.getTable() ); - } - - @Test - public void testSecondaryTableIndex(){ - PersistentClass entity = configuration().getClassMapping( Book.class.getName() ); - - Join join = (Join)entity.getJoinIterator().next(); - Iterator itr = join.getTable().getIndexIterator(); - assertTrue( itr.hasNext() ); - Index index = itr.next(); - assertFalse( itr.hasNext() ); - assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); - assertEquals( 2, index.getColumnSpan() ); - Iterator columnIterator = index.getColumnIterator(); - Column column = columnIterator.next(); - assertEquals( "summ_size", column.getName() ); - column = columnIterator.next(); - assertEquals( "text", column.getName() ); - assertSame( join.getTable(), index.getTable() ); - - } - - @Test - public void testCollectionTableIndex(){ - PersistentClass entity = configuration().getClassMapping( WealthyPerson.class.getName() ); - Property property = entity.getProperty( "explicitVacationHomes" ); - Set set = (Set)property.getValue(); - Table collectionTable = set.getCollectionTable(); - - Iterator itr = collectionTable.getIndexIterator(); - assertTrue( itr.hasNext() ); - Index index = itr.next(); - assertFalse( itr.hasNext() ); - assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); - assertEquals( 2, index.getColumnSpan() ); - Iterator columnIterator = index.getColumnIterator(); - Column column = columnIterator.next(); - assertEquals( "countryName", column.getName() ); - column = columnIterator.next(); - assertEquals( "type_id", column.getName() ); - assertSame( collectionTable, index.getTable() ); - - } - - @Test - public void testJoinTableIndex(){ - PersistentClass entity = configuration().getClassMapping( Alias.class.getName() ); - Property property = entity.getProperty( "characters" ); - Bag set = (Bag)property.getValue(); - Table collectionTable = set.getCollectionTable(); - - Iterator itr = collectionTable.getIndexIterator(); - assertTrue( itr.hasNext() ); - Index index = itr.next(); - assertFalse( itr.hasNext() ); - assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); - assertEquals( 1, index.getColumnSpan() ); - Iterator columnIterator = index.getColumnIterator(); - Column column = columnIterator.next(); - assertEquals( "characters_id", column.getName() ); - assertSame( collectionTable, index.getTable() ); - } - -// @Test - public void testTableGeneratorIndex(){ - //todo - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/OrmXmlIndexTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/OrmXmlIndexTest.java new file mode 100644 index 0000000000..2772b5b8aa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/OrmXmlIndexTest.java @@ -0,0 +1,15 @@ +package org.hibernate.test.annotations.index.jpa; + + +import org.hibernate.testing.FailureExpectedWithNewMetamodel; + +/** + * @author Strong Liu + */ +@FailureExpectedWithNewMetamodel +public class OrmXmlIndexTest extends AbstractJPAIndexTest { + @Override + protected String[] getXmlFiles() { + return new String[] { "org/hibernate/test/annotations/index/jpa/orm-index.xml" }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java index 59702bab9b..285a7bd0ef 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java @@ -25,14 +25,16 @@ package org.hibernate.test.annotations.xml.ejb3; import java.io.InputStream; +import org.hibernate.InvalidMappingException; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.xml.UnsupportedOrmXsdVersionException; + import org.junit.Test; -import org.hibernate.MappingException; -import org.hibernate.cfg.Configuration; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @TestForIssue(jiraKey = "HHH-6271") public class NonExistentOrmVersionTest extends BaseCoreFunctionalTestCase { @@ -45,14 +47,11 @@ public class NonExistentOrmVersionTest extends BaseCoreFunctionalTestCase { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( xmlFileName ); config.addInputStream( is ); config.buildMappings(); + fail( "Expecting failure due to unsupported xsd version" ); } - catch ( MappingException mappingException ) { - Throwable cause = mappingException.getCause(); - assertTrue( - cause.getMessage().contains( - "Value '3.0' of attribute 'version' of element 'entity-mappings' is not valid" - ) - ); + catch ( InvalidMappingException expected ) { + } + catch ( UnsupportedOrmXsdVersionException expected ) { } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/constraint/ConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/test/constraint/ConstraintTest.java index a80db3e4fc..2a0f1beaa5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/constraint/ConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/constraint/ConstraintTest.java @@ -20,57 +20,103 @@ */ package org.hibernate.test.constraint; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -import org.junit.Test; - -import org.hibernate.metamodel.spi.relational.Column; -import org.hibernate.test.util.SchemaUtil; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -/** - * HHH-7797 re-wrote the way dialects handle unique constraints. Test - * variations of unique & not null to ensure the constraints are created - * correctly for each dialect. - * - * @author Brett Meyer - */ -@TestForIssue( jiraKey = "HHH-7797" ) +import java.util.Iterator; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.junit.Test; + +import org.hibernate.mapping.Column; +import org.hibernate.mapping.ForeignKey; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +@FailureExpectedWithNewMetamodel public class ConstraintTest extends BaseCoreFunctionalTestCase { + private static final int MAX_NAME_LENGTH = 30; + + private static final String EXPLICIT_FK_NAME = "fk_explicit"; + + private static final String EXPLICIT_UK_NAME = "uk_explicit"; + @Override protected Class[] getAnnotatedClasses() { return new Class[] { - Entity1.class + DataPoint.class, DataPoint2.class }; } @Test - public void testConstraints() { - - Column column = SchemaUtil.getColumn( Entity1.class, "foo1", metadata() ); + @TestForIssue( jiraKey = "HHH-7797" ) + public void testUniqueConstraints() { + Column column = (Column) configuration().getClassMapping( DataPoint.class.getName() ) + .getProperty( "foo1" ).getColumnIterator().next(); assertFalse( column.isNullable() ); assertTrue( column.isUnique() ); - column = SchemaUtil.getColumn( Entity1.class, "foo2", metadata() ); + column = (Column) configuration().getClassMapping( DataPoint.class.getName() ) + .getProperty( "foo2" ).getColumnIterator().next(); assertTrue( column.isNullable() ); assertTrue( column.isUnique() ); - column = SchemaUtil.getColumn( Entity1.class, "id", metadata() ); + column = (Column) configuration().getClassMapping( DataPoint.class.getName() ) + .getProperty( "id" ).getColumnIterator().next(); assertFalse( column.isNullable() ); assertTrue( column.isUnique() ); } + @Test + @TestForIssue( jiraKey = "HHH-1904" ) + public void testConstraintNameLength() { + Iterator tableItr = configuration().getTableMappings(); + while (tableItr.hasNext()) { + org.hibernate.mapping.Table table = tableItr.next(); + + Iterator fkItr = table.getForeignKeyIterator(); + while (fkItr.hasNext()) { + ForeignKey fk = (ForeignKey) fkItr.next(); + assertTrue( fk.getName().length() <= MAX_NAME_LENGTH ); + + // ensure the randomly generated constraint name doesn't + // happen if explicitly given + Column column = fk.getColumn( 0 ); + if ( column.getName().equals( "explicit" ) ) { + assertEquals( fk.getName(), EXPLICIT_FK_NAME ); + } + } + + Iterator ukItr = table.getUniqueKeyIterator(); + while (ukItr.hasNext()) { + UniqueKey uk = (UniqueKey) ukItr.next(); + assertTrue( uk.getName().length() <= MAX_NAME_LENGTH ); + + // ensure the randomly generated constraint name doesn't + // happen if explicitly given + Column column = uk.getColumn( 0 ); + if ( column.getName().equals( "explicit" ) ) { + assertEquals( uk.getName(), EXPLICIT_UK_NAME ); + } + } + } + } + @Entity - @Table( name = "Entity1" ) - public static class Entity1 { + @Table( name = "DataPoint", uniqueConstraints = { + @UniqueConstraint( name = EXPLICIT_UK_NAME, columnNames = { "explicit" } ) + } ) + public static class DataPoint { @Id @GeneratedValue @javax.persistence.Column( nullable = false, unique = true) @@ -81,5 +127,22 @@ public class ConstraintTest extends BaseCoreFunctionalTestCase { @javax.persistence.Column( nullable = true, unique = true) public String foo2; + + public String explicit; + } + + @Entity + @Table( name = "DataPoint2" ) + public static class DataPoint2 { + @Id + @GeneratedValue + public long id; + + @OneToOne + public DataPoint dp; + + @OneToOne + @org.hibernate.annotations.ForeignKey(name = EXPLICIT_FK_NAME) + public DataPoint explicit; } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java index 2bdfefb256..e2a7d65bda 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java @@ -12,6 +12,7 @@ import org.hibernate.Hibernate; import org.hibernate.ScrollableResults; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.SQLServerDialect; @@ -42,7 +43,7 @@ public class HQLScrollFetchTest extends BaseCoreFunctionalTestCase { } @Test - @SkipForDialect( { SQLServerDialect.class, Oracle8iDialect.class, H2Dialect.class } ) + @SkipForDialect( { SQLServerDialect.class, Oracle8iDialect.class, H2Dialect.class, DB2Dialect.class } ) public void testScroll() { Session s = openSession(); ScrollableResults results = s.createQuery( QUERY ).scroll(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/StringCompositeKey.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/StringCompositeKey.java index 531c103836..01024f0b38 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/StringCompositeKey.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/StringCompositeKey.java @@ -1,6 +1,8 @@ package org.hibernate.test.querycache; import java.io.Serializable; + +import javax.persistence.Column; import javax.persistence.Embeddable; @Embeddable @@ -8,13 +10,20 @@ public class StringCompositeKey implements Serializable { private static final long serialVersionUID = 1L; - private String substation; + private String substation; private String deviceType; private String device; + + private String analog; - public String getSubstation() { + // For some dialects, the sum of a primary key column lengths cannot + // be larger than 255 (DB2). Restrict them to a sufficiently + // small size. See HHH-8085. + + @Column( length = 50 ) + public String getSubstation() { return substation; } @@ -22,6 +31,7 @@ public class StringCompositeKey implements Serializable { this.substation = substation; } + @Column( length = 50 ) public String getDeviceType() { return deviceType; } @@ -30,6 +40,7 @@ public class StringCompositeKey implements Serializable { this.deviceType = deviceType; } + @Column( length = 50 ) public String getDevice() { return device; } @@ -38,6 +49,7 @@ public class StringCompositeKey implements Serializable { this.device = device; } + @Column( length = 50 ) public String getAnalog() { return analog; } @@ -45,6 +57,4 @@ public class StringCompositeKey implements Serializable { public void setAnalog(String analog) { this.analog = analog; } - - private String analog; } \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/test/annotations/index/jpa/orm-index.xml b/hibernate-core/src/test/resources/org/hibernate/test/annotations/index/jpa/orm-index.xml new file mode 100644 index 0000000000..39726f6356 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/annotations/index/jpa/orm-index.xml @@ -0,0 +1,67 @@ + + + + org.hibernate.test.annotations.index.jpa + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/NamedInputStream.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/NamedInputStream.java index 7141f55f98..733455bf7e 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/NamedInputStream.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/NamedInputStream.java @@ -26,10 +26,12 @@ package org.hibernate.ejb.packaging; import java.io.InputStream; /** - * @deprecated Use {@link org.hibernate.jpa.packaging.spi.NamedInputStream} instead + * @deprecated Doubly deprecated actually :) Moved to {@link org.hibernate.jpa.boot.spi.NamedInputStream} + * due to package renaming (org.hibernate.ejb -> org.hibernate.jpa). But also, the role fulfilled by this class + * was moved to the new {@link org.hibernate.jpa.boot.spi.InputStreamAccess} contract. */ @Deprecated -public class NamedInputStream extends org.hibernate.jpa.packaging.spi.NamedInputStream { +public class NamedInputStream extends org.hibernate.jpa.boot.spi.NamedInputStream { public NamedInputStream(String name, InputStream stream) { super( name, stream ); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/Scanner.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/Scanner.java index 3a2cd4e46a..a452e66495 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/Scanner.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/packaging/Scanner.java @@ -24,8 +24,8 @@ package org.hibernate.ejb.packaging; /** - * @deprecated Use {@link org.hibernate.jpa.packaging.spi.Scanner} instead + * @deprecated Use {@link org.hibernate.jpa.boot.scan.spi.Scanner} instead */ @Deprecated -public interface Scanner extends org.hibernate.jpa.packaging.spi.Scanner { +public interface Scanner extends org.hibernate.jpa.boot.scan.spi.Scanner { } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitorFactory.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ArchiveHelper.java similarity index 63% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitorFactory.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ArchiveHelper.java index 45ca7f8e1b..76caf4db9c 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitorFactory.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ArchiveHelper.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,29 +21,26 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; +package org.hibernate.jpa.boot.archive.internal; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.util.LinkedList; import java.util.List; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.jpa.internal.EntityManagerMessageLogger; import org.jboss.logging.Logger; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; + /** * @author Emmanuel Bernard - * @author Brett Meyer + * @author Steve Ebersole */ -public class JarVisitorFactory { - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - JarVisitorFactory.class.getName()); +public class ArchiveHelper { + private static final Logger log = Logger.getLogger( ArchiveHelper.class ); /** * Get the JAR URL of the JAR containing the given entry @@ -51,7 +50,6 @@ public class JarVisitorFactory { * @param entry file known to be in the JAR * @return the JAR URL * @throws IllegalArgumentException if none URL is found - * TODO move to a ScannerHelper service? */ public static URL getJarURLFromURLEntry(URL url, String entry) throws IllegalArgumentException { URL jarUrl; @@ -106,7 +104,7 @@ public class JarVisitorFactory { "Unable to determine JAR Url from " + url + ". Cause: " + e.getMessage() ); } - LOG.trace("JAR URL from URL Entry: " + url + " >> " + jarUrl); + log.trace("JAR URL from URL Entry: " + url + " >> " + jarUrl); return jarUrl; } @@ -114,7 +112,6 @@ public class JarVisitorFactory { * get the URL from a given path string * * @throws IllegalArgumentException is something goes wrong - * TODO move to a ScannerHelper service? */ public static URL getURLFromPath(String jarPath) { URL jarUrl; @@ -134,70 +131,40 @@ public class JarVisitorFactory { return jarUrl; } - /** - * Get a JarVisitor to the jar jarPath applying the given filters - * - * Method used in a non-managed environment - * - * @throws IllegalArgumentException if the jarPath is incorrect - */ - public static JarVisitor getVisitor(String jarPath, Filter[] filters) throws IllegalArgumentException { - File file = new File( jarPath ); - if ( file.isFile() ) { - return new InputStreamZippedJarVisitor( jarPath, filters ); + public static String unqualifiedJarFileName(URL jarUrl) { + // todo : weak algorithm subject to AOOBE + String fileName = jarUrl.getFile(); + int exclamation = fileName.lastIndexOf( "!" ); + if (exclamation != -1) { + fileName = fileName.substring( 0, exclamation ); } - else { - return new ExplodedJarVisitor( jarPath, filters ); + + int slash = fileName.lastIndexOf( "/" ); + if ( slash != -1 ) { + fileName = fileName.substring( + fileName.lastIndexOf( "/" ) + 1, + fileName.length() + ); + } + + if ( fileName.length() > 4 && fileName.endsWith( "ar" ) && fileName.charAt( fileName.length() - 4 ) == '.' ) { + fileName = fileName.substring( 0, fileName.length() - 4 ); + } + + return fileName; + } + + public static byte[] getBytesFromInputStreamSafely(InputStream inputStream) { + try { + return getBytesFromInputStream( inputStream ); + } + catch (IOException e) { + throw new ArchiveException( "Unable to extract bytes from InputStream", e ); } } - /** - * Build a JarVisitor on the given JAR URL applying the given filters - * - * @throws IllegalArgumentException if the URL is malformed - */ - public static JarVisitor getVisitor(URL jarUrl, Filter[] filters) throws IllegalArgumentException { - return getVisitor( jarUrl, filters, "" ); - } - - public static JarVisitor getVisitor(URL jarUrl, Filter[] filters, String entry) throws IllegalArgumentException { - String protocol = jarUrl.getProtocol(); - if ( "jar".equals( protocol ) ) { - return new JarProtocolVisitor( jarUrl, filters, entry ); - } - else if ( StringHelper.isEmpty( protocol ) || "file".equals( protocol ) || "vfszip".equals( protocol ) || "vfsfile".equals( protocol ) ) { - File file; - try { - final String filePart = jarUrl.getFile(); - if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { - //unescaped (from the container), keep as is - file = new File( jarUrl.getFile() ); - } - else { - file = new File( jarUrl.toURI().getSchemeSpecificPart() ); - } - } - catch (URISyntaxException e) { - throw new IllegalArgumentException( - "Unable to visit JAR " + jarUrl + ". Cause: " + e.getMessage(), e - ); - } - - if ( file.isDirectory() ) { - return new ExplodedJarVisitor( jarUrl, filters, entry ); - } - else { - return new FileZippedJarVisitor( jarUrl, filters, entry ); - } - } - else { - //let's assume the url can return the jar as a zip stream - return new InputStreamZippedJarVisitor( jarUrl, filters, entry ); - } - } - - // Optimized by HHH-7835 public static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException { + // Optimized by HHH-7835 int size; List data = new LinkedList(); int bufferSize = 4096; diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ExplodedArchiveDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ExplodedArchiveDescriptor.java new file mode 100644 index 0000000000..50a64a1a2f --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/ExplodedArchiveDescriptor.java @@ -0,0 +1,212 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.internal; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import org.jboss.logging.Logger; + +import org.hibernate.jpa.boot.archive.spi.AbstractArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; +import org.hibernate.jpa.boot.internal.FileInputStreamAccess; +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.internal.EntityManagerMessageLogger; + +/** + * @author Steve Ebersole + */ +public class ExplodedArchiveDescriptor extends AbstractArchiveDescriptor { + private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( + EntityManagerMessageLogger.class, + ExplodedArchiveDescriptor.class.getName() + ); + + public ExplodedArchiveDescriptor( + ArchiveDescriptorFactory archiveDescriptorFactory, + URL archiveUrl, + String entryBasePrefix) { + super( archiveDescriptorFactory, archiveUrl, entryBasePrefix ); + } + + @Override + public void visitArchive(ArchiveContext context) { + final File rootDirectory = resolveRootDirectory(); + if ( rootDirectory == null ) { + return; + } + + if ( rootDirectory.isDirectory() ) { + processDirectory( rootDirectory, null, context ); + } + else { + //assume zipped file + processZippedRoot( rootDirectory, context ); + } + } + + private File resolveRootDirectory() { + final File archiveUrlDirectory; + try { + final String filePart = getArchiveUrl().getFile(); + if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { + //unescaped (from the container), keep as is + archiveUrlDirectory = new File( filePart ); + } + else { + archiveUrlDirectory = new File( getArchiveUrl().toURI().getSchemeSpecificPart() ); + } + } + catch (URISyntaxException e) { + LOG.malformedUrl( getArchiveUrl(), e ); + return null; + } + + if ( !archiveUrlDirectory.exists() ) { + LOG.explodedJarDoesNotExist( getArchiveUrl() ); + return null; + } + if ( !archiveUrlDirectory.isDirectory() ) { + LOG.explodedJarNotDirectory( getArchiveUrl() ); + return null; + } + + final String entryBase = getEntryBasePrefix(); + if ( entryBase != null && entryBase.length() > 0 && ! "/".equals( entryBase ) ) { + return new File( archiveUrlDirectory, entryBase ); + } + else { + return archiveUrlDirectory; + } + } + + private void processDirectory( + File directory, + String path, + ArchiveContext context) { + if ( directory == null ) { + return; + } + + final File[] files = directory.listFiles(); + if ( files == null ) { + return; + } + + path = path == null ? "" : path + "/"; + for ( final File localFile : files ) { + if ( !localFile.exists() ) { + // should never happen conceptually, but... + continue; + } + + if ( localFile.isDirectory() ) { + processDirectory( localFile, path + localFile.getName(), context ); + continue; + } + + final String name = localFile.getAbsolutePath(); + final String relativeName = path + localFile.getName(); + final InputStreamAccess inputStreamAccess = new FileInputStreamAccess( name, localFile ); + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + context.obtainArchiveEntryHandler( entry ).handleEntry( entry, context ); + } + } + + private void processZippedRoot(File rootFile, ArchiveContext context) { + try { + final JarFile jarFile = new JarFile(rootFile); + final Enumeration entries = jarFile.entries(); + while ( entries.hasMoreElements() ) { + final ZipEntry zipEntry = entries.nextElement(); + if ( zipEntry.isDirectory() ) { + continue; + } + + final String name = extractName( zipEntry ); + final String relativeName = extractRelativeName( zipEntry ); + final InputStreamAccess inputStreamAccess; + try { + inputStreamAccess = buildByteBasedInputStreamAccess( name, jarFile.getInputStream( zipEntry ) ); + } + catch (IOException e) { + throw new ArchiveException( + String.format( + "Unable to access stream from jar file [%s] for entry [%s]", + jarFile.getName(), + zipEntry.getName() + ) + ); + } + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + context.obtainArchiveEntryHandler( entry ).handleEntry( entry, context ); + } + } + catch (IOException e) { + throw new ArchiveException( "Error accessing jar file [" + rootFile.getAbsolutePath() + "]", e ); + } + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarFileBasedArchiveDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarFileBasedArchiveDescriptor.java new file mode 100644 index 0000000000..d601d69ae2 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarFileBasedArchiveDescriptor.java @@ -0,0 +1,193 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.internal; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.zip.ZipEntry; + +import org.jboss.logging.Logger; + +import org.hibernate.jpa.boot.archive.spi.AbstractArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntryHandler; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; +import org.hibernate.jpa.internal.EntityManagerMessageLogger; +import org.hibernate.jpa.boot.spi.InputStreamAccess; + +/** + * An ArchiveDescriptor implementation leveraging the {@link JarFile} API for processing. + * + * @author Steve Ebersole + */ +public class JarFileBasedArchiveDescriptor extends AbstractArchiveDescriptor { + private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( + EntityManagerMessageLogger.class, + JarFileBasedArchiveDescriptor.class.getName() + ); + + public JarFileBasedArchiveDescriptor( + ArchiveDescriptorFactory archiveDescriptorFactory, + URL archiveUrl, + String entry) { + super( archiveDescriptorFactory, archiveUrl, entry ); + } + + @Override + public void visitArchive(ArchiveContext context) { + final JarFile jarFile = resolveJarFileReference(); + if ( jarFile == null ) { + return; + } + + final Enumeration zipEntries = jarFile.entries(); + while ( zipEntries.hasMoreElements() ) { + final ZipEntry zipEntry = zipEntries.nextElement(); + final String entryName = extractName( zipEntry ); + + if ( getEntryBasePrefix() != null && ! entryName.startsWith( getEntryBasePrefix() ) ) { + continue; + } + if ( zipEntry.isDirectory() ) { + continue; + } + + if ( entryName.equals( getEntryBasePrefix() ) ) { + // exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) + // + // This algorithm assumes that the zipped file is only the URL root (including entry), not + // just any random entry + try { + InputStream is = new BufferedInputStream( jarFile.getInputStream( zipEntry ) ); + try { + final JarInputStream jarInputStream = new JarInputStream( is ); + ZipEntry subZipEntry = jarInputStream.getNextEntry(); + while ( subZipEntry != null ) { + if ( ! subZipEntry.isDirectory() ) { + + final String name = extractName( subZipEntry ); + final String relativeName = extractRelativeName( subZipEntry ); + final InputStreamAccess inputStreamAccess + = buildByteBasedInputStreamAccess( name, jarInputStream ); + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); + entryHandler.handleEntry( entry, context ); + } + + subZipEntry = jarInputStream.getNextEntry(); + } + } + finally { + is.close(); + } + } + catch (Exception e) { + throw new ArchiveException( "Error accessing JarFile entry [" + zipEntry.getName() + "]", e ); + } + } + else { + final String name = extractName( zipEntry ); + final String relativeName = extractRelativeName( zipEntry ); + final InputStreamAccess inputStreamAccess; + try { + inputStreamAccess = buildByteBasedInputStreamAccess( name, jarFile.getInputStream( zipEntry ) ); + } + catch (IOException e) { + throw new ArchiveException( + String.format( + "Unable to access stream from jar file [%s] for entry [%s]", + jarFile.getName(), + zipEntry.getName() + ) + ); + } + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return name; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + final ArchiveEntryHandler entryHandler = context.obtainArchiveEntryHandler( entry ); + entryHandler.handleEntry( entry, context ); + } + } + } + + private JarFile resolveJarFileReference() { + try { + String filePart = getArchiveUrl().getFile(); + if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { + // unescaped (from the container), keep as is + return new JarFile( getArchiveUrl().getFile() ); + } + else { + return new JarFile( getArchiveUrl().toURI().getSchemeSpecificPart() ); + } + } + catch (IOException e) { + LOG.unableToFindFile( getArchiveUrl(), e ); + } + catch (URISyntaxException e) { + LOG.malformedUrlWarning( getArchiveUrl(), e ); + } + return null; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarInputStreamBasedArchiveDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarInputStreamBasedArchiveDescriptor.java new file mode 100644 index 0000000000..3b7b338535 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarInputStreamBasedArchiveDescriptor.java @@ -0,0 +1,168 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.internal; + +import java.io.IOException; +import java.net.URL; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.zip.ZipEntry; + +import org.jboss.logging.Logger; + +import org.hibernate.jpa.boot.archive.spi.AbstractArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.internal.EntityManagerMessageLogger; + +/** + * An ArchiveDescriptor implementation that works on archives accessible through a {@link java.util.jar.JarInputStream}. + * NOTE : This is less efficient implementation than {@link JarFileBasedArchiveDescriptor} + * + * @author Emmanuel Bernard + * @author Steve Ebersole + */ +public class JarInputStreamBasedArchiveDescriptor extends AbstractArchiveDescriptor { + private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( + EntityManagerMessageLogger.class, + JarInputStreamBasedArchiveDescriptor.class.getName() + ); + + public JarInputStreamBasedArchiveDescriptor( + ArchiveDescriptorFactory archiveDescriptorFactory, + URL url, + String entry) { + super( archiveDescriptorFactory, url, entry ); + } + + @Override + public void visitArchive(ArchiveContext context) { + final JarInputStream jarInputStream; + try { + jarInputStream = new JarInputStream( getArchiveUrl().openStream() ); + } + catch (Exception e) { + //really should catch IOException but Eclipse is buggy and raise NPE... + LOG.unableToFindFile( getArchiveUrl(), e ); + return; + } + + try { + JarEntry jarEntry; + while ( ( jarEntry = jarInputStream.getNextJarEntry() ) != null ) { + String jarEntryName = jarEntry.getName(); + if ( getEntryBasePrefix() != null && ! jarEntryName.startsWith( getEntryBasePrefix() ) ) { + continue; + } + + if ( jarEntry.isDirectory() ) { + continue; + } + + if ( jarEntryName.equals( getEntryBasePrefix() ) ) { + // exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) + // + // This algorithm assumes that the zipped file is only the URL root (including entry), not + // just any random entry + try { + final JarInputStream subJarInputStream = new JarInputStream( jarInputStream ); + try { + ZipEntry subZipEntry = jarInputStream.getNextEntry(); + while (subZipEntry != null) { + if ( ! subZipEntry.isDirectory() ) { + final String subName = extractName( subZipEntry ); + final InputStreamAccess inputStreamAccess + = buildByteBasedInputStreamAccess( subName, subJarInputStream ); + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return subName; + } + + @Override + public String getNameWithinArchive() { + return subName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + context.obtainArchiveEntryHandler( entry ).handleEntry( entry, context ); + } + subZipEntry = jarInputStream.getNextJarEntry(); + } + } + finally { + subJarInputStream.close(); + } + } + catch (Exception e) { + throw new ArchiveException( "Error accessing nested jar", e ); + } + } + else { + final String entryName = extractName( jarEntry ); + final InputStreamAccess inputStreamAccess + = buildByteBasedInputStreamAccess( entryName, jarInputStream ); + + final String relativeName = extractRelativeName( jarEntry ); + + final ArchiveEntry entry = new ArchiveEntry() { + @Override + public String getName() { + return entryName; + } + + @Override + public String getNameWithinArchive() { + return relativeName; + } + + @Override + public InputStreamAccess getStreamAccess() { + return inputStreamAccess; + } + }; + + context.obtainArchiveEntryHandler( entry ).handleEntry( entry, context ); + } + } + + jarInputStream.close(); + } + catch (IOException ioe) { + throw new ArchiveException( + String.format( "Error accessing JarInputStream [%s]", getArchiveUrl() ), + ioe + ); + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarProtocolArchiveDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarProtocolArchiveDescriptor.java new file mode 100644 index 0000000000..4eaa7d8748 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/JarProtocolArchiveDescriptor.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.internal; + +import java.net.URL; + +import org.hibernate.annotations.common.AssertionFailure; +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; + +/** + * An ArchiveDescriptor implementation for handling archives whose url reported a JAR protocol (i.e., jar://). + * + * @author Steve Ebersole + */ +public class JarProtocolArchiveDescriptor implements ArchiveDescriptor { + private final ArchiveDescriptor delegateDescriptor; + + public JarProtocolArchiveDescriptor( + ArchiveDescriptorFactory archiveDescriptorFactory, + URL url, + String incomingEntry) { + if ( incomingEntry != null && incomingEntry.length() > 0 ) { + throw new IllegalArgumentException( "jar:jar: not supported: " + url ); + } + + final String urlFile = url.getFile(); + final int subEntryIndex = urlFile.lastIndexOf( "!" ); + if ( subEntryIndex == -1 ) { + throw new AssertionFailure( "JAR URL does not contain '!/' :" + url ); + } + + final String subEntry; + if ( subEntryIndex + 1 >= urlFile.length() ) { + subEntry = ""; + } + else { + subEntry = urlFile.substring( subEntryIndex + 1 ); + } + + URL fileUrl = archiveDescriptorFactory.getJarURLFromURLEntry( url, subEntry ); + delegateDescriptor = archiveDescriptorFactory.buildArchiveDescriptor( fileUrl, subEntry ); + } + + @Override + public void visitArchive(ArchiveContext context) { + delegateDescriptor.visitArchive( context ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/StandardArchiveDescriptorFactory.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/StandardArchiveDescriptorFactory.java new file mode 100644 index 0000000000..e502570140 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/internal/StandardArchiveDescriptorFactory.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.internal; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; + +/** + * @author Emmanuel Bernard + * @author Steve Ebersole + */ +public class StandardArchiveDescriptorFactory implements ArchiveDescriptorFactory { + public static final StandardArchiveDescriptorFactory INSTANCE = new StandardArchiveDescriptorFactory(); + + @Override + public ArchiveDescriptor buildArchiveDescriptor(URL url) { + return buildArchiveDescriptor( url, "" ); + } + + @Override + public ArchiveDescriptor buildArchiveDescriptor(URL url, String entry) { + final String protocol = url.getProtocol(); + if ( "jar".equals( protocol ) ) { + return new JarProtocolArchiveDescriptor( this, url, entry ); + } + else if ( StringHelper.isEmpty( protocol ) + || "file".equals( protocol ) + || "vfszip".equals( protocol ) + || "vfsfile".equals( protocol ) ) { + final File file; + try { + final String filePart = url.getFile(); + if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { + //unescaped (from the container), keep as is + file = new File( url.getFile() ); + } + else { + file = new File( url.toURI().getSchemeSpecificPart() ); + } + + if ( ! file.exists() ) { + throw new IllegalArgumentException( + String.format( + "File [%s] referenced by given URL [%s] does not exist", + filePart, + url.toExternalForm() + ) + ); + } + } + catch (URISyntaxException e) { + throw new IllegalArgumentException( + "Unable to visit JAR " + url + ". Cause: " + e.getMessage(), e + ); + } + + if ( file.isDirectory() ) { + return new ExplodedArchiveDescriptor( this, url, entry ); + } + else { + return new JarFileBasedArchiveDescriptor( this, url, entry ); + } + } + else { + //let's assume the url can return the jar as a zip stream + return new JarInputStreamBasedArchiveDescriptor( this, url, entry ); + } + } + + @Override + public URL getJarURLFromURLEntry(URL url, String entry) throws IllegalArgumentException { + return ArchiveHelper.getJarURLFromURLEntry( url, entry ); + } + + @Override + public URL getURLFromPath(String jarPath) { + return ArchiveHelper.getURLFromPath( jarPath ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/AbstractArchiveDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/AbstractArchiveDescriptor.java new file mode 100644 index 0000000000..00f92c6a0b --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/AbstractArchiveDescriptor.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.spi; + +import java.io.InputStream; +import java.net.URL; +import java.util.zip.ZipEntry; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.jpa.boot.internal.ByteArrayInputStreamAccess; +import org.hibernate.jpa.boot.archive.internal.ArchiveHelper; +import org.hibernate.jpa.boot.spi.InputStreamAccess; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractArchiveDescriptor implements ArchiveDescriptor { + private final ArchiveDescriptorFactory archiveDescriptorFactory; + private final URL archiveUrl; + private final String entryBasePrefix; + + protected AbstractArchiveDescriptor( + ArchiveDescriptorFactory archiveDescriptorFactory, + URL archiveUrl, + String entryBasePrefix) { + this.archiveDescriptorFactory = archiveDescriptorFactory; + this.archiveUrl = archiveUrl; + this.entryBasePrefix = normalizeEntryBasePrefix( entryBasePrefix ); + } + + private static String normalizeEntryBasePrefix(String entryBasePrefix) { + if ( StringHelper.isEmpty( entryBasePrefix ) || entryBasePrefix.length() == 1 ) { + return null; + } + + return entryBasePrefix.startsWith( "/" ) ? entryBasePrefix.substring( 1 ) : entryBasePrefix; + } + + protected ArchiveDescriptorFactory getArchiveDescriptorFactory() { + return archiveDescriptorFactory; + } + + protected URL getArchiveUrl() { + return archiveUrl; + } + + protected String getEntryBasePrefix() { + return entryBasePrefix; + } + + protected String extractRelativeName(ZipEntry zipEntry) { + final String entryName = extractName( zipEntry ); + return entryBasePrefix == null ? entryName : entryName.substring( entryBasePrefix.length() ); + } + + protected String extractName(ZipEntry zipEntry) { + return normalizePathName( zipEntry.getName() ); + } + + protected String normalizePathName(String pathName) { + return pathName.startsWith( "/" ) ? pathName.substring( 1 ) : pathName; + } + + protected InputStreamAccess buildByteBasedInputStreamAccess(final String name, InputStream inputStream) { + // because of how jar InputStreams work we need to extract the bytes immediately. However, we + // do delay the creation of the ByteArrayInputStreams until needed + final byte[] bytes = ArchiveHelper.getBytesFromInputStreamSafely( inputStream ); + return new ByteArrayInputStreamAccess( name, bytes ); + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveContext.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveContext.java new file mode 100644 index 0000000000..8674cc6bb5 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveContext.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.spi; + +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; + +/** +* @author Steve Ebersole +*/ +public interface ArchiveContext { + public PersistenceUnitDescriptor getPersistenceUnitDescriptor(); + + public boolean isRootUrl(); + + public ArchiveEntryHandler obtainArchiveEntryHandler(ArchiveEntry entry); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Filter.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptor.java similarity index 67% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Filter.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptor.java index 0d16548108..b9d07dee0d 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Filter.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptor.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,22 +21,14 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; - +package org.hibernate.jpa.boot.archive.spi; /** - * Filter used when searching elements in a JAR + * Contract for visiting an archive, which might be a jar, a zip, an exploded directory, etc. * + * @author Steve Ebersole * @author Emmanuel Bernard */ -public abstract class Filter { - private boolean retrieveStream; - - protected Filter(boolean retrieveStream) { - this.retrieveStream = retrieveStream; - } - - public boolean getStream() { - return retrieveStream; - } +public interface ArchiveDescriptor { + public void visitArchive(ArchiveContext archiveContext); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileFilter.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptorFactory.java similarity index 58% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileFilter.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptorFactory.java index 8c8575be3f..441b531b25 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileFilter.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveDescriptorFactory.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,25 +21,19 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; +package org.hibernate.jpa.boot.archive.spi; +import java.net.URL; /** - * Filter use to match a file by its name + * Contract for building ArchiveDescriptor instances. * - * @author Emmanuel Bernard + * @author Steve Ebersole */ -public abstract class FileFilter extends Filter { +public interface ArchiveDescriptorFactory { + public ArchiveDescriptor buildArchiveDescriptor(URL url); + public ArchiveDescriptor buildArchiveDescriptor(URL jarUrl, String entry); - /** - * @param retrieveStream Give back an open stream to the matching element or not - */ - public FileFilter(boolean retrieveStream) { - super( retrieveStream ); - } - - /** - * Return true if the fully qualified file name match - */ - public abstract boolean accept(String name); - } \ No newline at end of file + public URL getJarURLFromURLEntry(URL url, String entry) throws IllegalArgumentException; + public URL getURLFromPath(String jarPath); +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/Origin.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntry.java similarity index 63% rename from hibernate-core/src/main/java/org/hibernate/internal/util/xml/Origin.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntry.java index 805cdd8168..7d7e9185e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/Origin.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntry.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -21,29 +21,35 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.internal.util.xml; +package org.hibernate.jpa.boot.archive.spi; -import java.io.Serializable; +import org.hibernate.jpa.boot.spi.InputStreamAccess; /** - * Describes the origin of an xml document + * Represent an entry in the archive. * * @author Steve Ebersole */ -public interface Origin extends Serializable { +public interface ArchiveEntry { /** - * Retrieve the type of origin. This is not a discrete set, but might be somethign like - * {@code file} for file protocol URLs, or {@code resource} for classpath resource lookups. + * Get the entry's name * - * @return The origin type. - */ - public String getType(); - - /** - * The name of the document origin. Interpretation is relative to the type, but might be the - * resource name or file URL. - * - * @return The name. + * @return */ public String getName(); + + /** + * Get the relative name of the entry within the archive. Typically what we are looking for here is + * the ClassLoader resource lookup name. + * + * @return + */ + public String getNameWithinArchive(); + + /** + * Get access to the stream for the entry + * + * @return + */ + public InputStreamAccess getStreamAccess(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntryHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntryHandler.java new file mode 100644 index 0000000000..3fce3402c0 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveEntryHandler.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.archive.spi; + +/** + * Handler for archive entries, based on the classified type of the entry + * + * @author Steve Ebersole + */ +public interface ArchiveEntryHandler { + public void handleEntry(ArchiveEntry entry, ArchiveContext context); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ClassFilter.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveException.java similarity index 63% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ClassFilter.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveException.java index 6ea6dc08bb..1257502318 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ClassFilter.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/archive/spi/ArchiveException.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,20 +21,19 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; +package org.hibernate.jpa.boot.archive.spi; +import org.hibernate.HibernateException; /** - * Filter on class elements - * - * @author Emmanuel Bernard - * @see JavaElementFilter + * @author Steve Ebersole */ -public abstract class ClassFilter extends JavaElementFilter { - /** - * @see JavaElementFilter#JavaElementFilter(boolean, Class[]) - */ - protected ClassFilter(boolean retrieveStream, Class[] annotations) { - super( retrieveStream, annotations ); +public class ArchiveException extends HibernateException { + public ArchiveException(String message) { + super( message ); } -} \ No newline at end of file + + public ArchiveException(String message, Throwable root) { + super( message, root ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ByteArrayInputStreamAccess.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ByteArrayInputStreamAccess.java new file mode 100644 index 0000000000..8b949206ce --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ByteArrayInputStreamAccess.java @@ -0,0 +1,60 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.internal; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.NamedInputStream; + +/** + * An InputStreamAccess implementation based on a byte array + * + * @author Steve Ebersole + */ +public class ByteArrayInputStreamAccess implements InputStreamAccess { + private final String name; + private final byte[] bytes; + + public ByteArrayInputStreamAccess(String name, byte[] bytes) { + this.name = name; + this.bytes = bytes; + } + + @Override + public String getStreamName() { + return name; + } + + @Override + public InputStream accessInputStream() { + return new ByteArrayInputStream( bytes ); + } + + @Override + public NamedInputStream asNamedInputStream() { + return new NamedInputStream( getStreamName(), accessInputStream() ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Entry.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ClassDescriptorImpl.java similarity index 53% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Entry.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ClassDescriptorImpl.java index 584bfcb15c..f8d15c1419 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/Entry.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/ClassDescriptorImpl.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,44 +21,48 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; -import java.io.InputStream; +package org.hibernate.jpa.boot.internal; + +import org.hibernate.jpa.boot.spi.ClassDescriptor; +import org.hibernate.jpa.boot.spi.InputStreamAccess; /** - * Represent a JAR entry - * Contains a name and an optional Input stream to the entry - * - * @author Emmanuel Bernard + * @author Steve Ebersole */ -public class Entry { - private String name; - private InputStream is; +public class ClassDescriptorImpl implements ClassDescriptor { + private final String name; + private final InputStreamAccess streamAccess; - public Entry(String name, InputStream is) { + public ClassDescriptorImpl(String name, InputStreamAccess streamAccess) { this.name = name; - this.is = is; + this.streamAccess = streamAccess; } + @Override public String getName() { return name; } - public InputStream getInputStream() { - return is; + @Override + public InputStreamAccess getStreamAccess() { + return streamAccess; } + @Override public boolean equals(Object o) { - if ( this == o ) return true; - if ( o == null || getClass() != o.getClass() ) return false; + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } - final Entry entry = (Entry) o; - - if ( !name.equals( entry.name ) ) return false; - - return true; + ClassDescriptorImpl that = (ClassDescriptorImpl) o; + return name.equals( that.name ); } + @Override public int hashCode() { return name.hashCode(); } -} \ No newline at end of file +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index fcf6f01d2a..f57d43652f 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -23,12 +23,17 @@ */ package org.hibernate.jpa.boot.internal; +import javax.persistence.AttributeConverter; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityNotFoundException; +import javax.persistence.PersistenceException; +import javax.persistence.spi.PersistenceUnitTransactionType; +import javax.sql.DataSource; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; -import java.lang.annotation.Annotation; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -42,18 +47,18 @@ import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; -import javax.persistence.Embeddable; -import javax.persistence.Entity; -import javax.persistence.EntityManagerFactory; -import javax.persistence.EntityNotFoundException; -import javax.persistence.MappedSuperclass; -import javax.persistence.PersistenceException; -import javax.persistence.spi.PersistenceUnitTransactionType; -import javax.sql.DataSource; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; + +import org.jboss.logging.Logger; import org.hibernate.Interceptor; +import org.hibernate.InvalidMappingException; import org.hibernate.MappingException; import org.hibernate.MappingNotFoundException; import org.hibernate.SessionFactory; @@ -86,29 +91,29 @@ import org.hibernate.jpa.boot.spi.IntegratorProvider; import org.hibernate.jpa.boot.spi.JpaUnifiedSettingsBuilder; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.event.spi.JpaIntegrator; -import org.hibernate.jpa.internal.schemagen.JpaSchemaGenerator; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import org.hibernate.jpa.internal.EntityManagerMessageLogger; +import org.hibernate.jpa.internal.schemagen.JpaSchemaGenerator; import org.hibernate.jpa.internal.util.LogHelper; import org.hibernate.jpa.internal.util.PersistenceUnitTransactionTypeHelper; -import org.hibernate.jpa.packaging.internal.NativeScanner; -import org.hibernate.jpa.packaging.spi.NamedInputStream; -import org.hibernate.jpa.packaging.spi.Scanner; +import org.hibernate.jpa.boot.scan.internal.StandardScanOptions; +import org.hibernate.jpa.boot.scan.internal.StandardScanner; +import org.hibernate.jpa.boot.spi.ClassDescriptor; +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; +import org.hibernate.jpa.boot.spi.NamedInputStream; +import org.hibernate.jpa.boot.spi.PackageDescriptor; +import org.hibernate.jpa.boot.scan.spi.ScanOptions; +import org.hibernate.jpa.boot.scan.spi.ScanResult; +import org.hibernate.jpa.boot.scan.spi.Scanner; import org.hibernate.jpa.spi.IdentifierGeneratorStrategyProvider; +import org.hibernate.metamodel.MetadataSources; import org.hibernate.metamodel.internal.source.annotations.util.JPADotNames; import org.hibernate.metamodel.internal.source.annotations.util.JandexHelper; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.secure.internal.JACCConfiguration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.CompositeIndex; -import org.jboss.jandex.DotName; -import org.jboss.jandex.Index; -import org.jboss.jandex.IndexView; -import org.jboss.jandex.Indexer; -import org.jboss.logging.Logger; /** * @author Steve Ebersole @@ -203,13 +208,14 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Next we do a preliminary pass at metadata processing, which involves: // 1) scanning - ScanResult scanResult = scan( bootstrapServiceRegistry ); + final ScanResult scanResult = scan( bootstrapServiceRegistry ); + final DeploymentResources deploymentResources = buildDeploymentResources( scanResult, bootstrapServiceRegistry ); // 2) building a Jandex index - Set collectedManagedClassNames = collectManagedClassNames( scanResult ); - IndexView jandexIndex = locateOrBuildJandexIndex( collectedManagedClassNames, scanResult.getPackageNames(), bootstrapServiceRegistry ); + final IndexView jandexIndex = locateOrBuildJandexIndex( deploymentResources ); // 3) building "metadata sources" to keep for later to use in building the SessionFactory - metadataSources = prepareMetadataSources( jandexIndex, collectedManagedClassNames, scanResult, bootstrapServiceRegistry ); + metadataSources = prepareMetadataSources( jandexIndex, deploymentResources, bootstrapServiceRegistry ); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ withValidatorFactory( configurationValues.get( AvailableSettings.VALIDATION_FACTORY ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -221,6 +227,241 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } } + private static interface DeploymentResources { + public Iterable getClassDescriptors(); + public Iterable getPackageDescriptors(); + public Iterable getMappingFileDescriptors(); + } + + private DeploymentResources buildDeploymentResources( + ScanResult scanResult, + BootstrapServiceRegistry bootstrapServiceRegistry) { + + // mapping files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final ArrayList mappingFileDescriptors = new ArrayList(); + + final Set nonLocatedMappingFileNames = new HashSet(); + final List explicitMappingFileNames = persistenceUnit.getMappingFileNames(); + if ( explicitMappingFileNames != null ) { + nonLocatedMappingFileNames.addAll( explicitMappingFileNames ); + } + + for ( MappingFileDescriptor mappingFileDescriptor : scanResult.getLocatedMappingFiles() ) { + mappingFileDescriptors.add( mappingFileDescriptor ); + nonLocatedMappingFileNames.remove( mappingFileDescriptor.getName() ); + } + + for ( String name : nonLocatedMappingFileNames ) { + MappingFileDescriptor descriptor = buildMappingFileDescriptor( name, bootstrapServiceRegistry ); + mappingFileDescriptors.add( descriptor ); + } + + + // classes and packages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final HashMap classDescriptorMap = new HashMap(); + final HashMap packageDescriptorMap = new HashMap(); + + for ( ClassDescriptor classDescriptor : scanResult.getLocatedClasses() ) { + classDescriptorMap.put( classDescriptor.getName(), classDescriptor ); + } + + for ( PackageDescriptor packageDescriptor : scanResult.getLocatedPackages() ) { + packageDescriptorMap.put( packageDescriptor.getName(), packageDescriptor ); + } + + final List explicitClassNames = persistenceUnit.getManagedClassNames(); + if ( explicitClassNames != null ) { + for ( String explicitClassName : explicitClassNames ) { + // IMPL NOTE : explicitClassNames can contain class or package names!!! + if ( classDescriptorMap.containsKey( explicitClassName ) ) { + continue; + } + if ( packageDescriptorMap.containsKey( explicitClassName ) ) { + continue; + } + + // try it as a class name first... + final String classFileName = explicitClassName.replace( '.', '/' ) + ".class"; + final URL classFileUrl = bootstrapServiceRegistry.getService( ClassLoaderService.class ) + .locateResource( classFileName ); + if ( classFileUrl != null ) { + classDescriptorMap.put( + explicitClassName, + new ClassDescriptorImpl( explicitClassName, new UrlInputStreamAccess( classFileUrl ) ) + ); + continue; + } + + // otherwise, try it as a package name + final String packageInfoFileName = explicitClassName.replace( '.', '/' ) + "/package-info.class"; + final URL packageInfoFileUrl = bootstrapServiceRegistry.getService( ClassLoaderService.class ) + .locateResource( packageInfoFileName ); + if ( packageInfoFileUrl != null ) { + packageDescriptorMap.put( + explicitClassName, + new PackageDescriptorImpl( explicitClassName, new UrlInputStreamAccess( packageInfoFileUrl ) ) + ); + continue; + } + + LOG.debugf( + "Unable to resolve class [%s] named in persistence unit [%s]", + explicitClassName, + persistenceUnit.getName() + ); + } + } + + return new DeploymentResources() { + @Override + public Iterable getClassDescriptors() { + return classDescriptorMap.values(); + } + + @Override + public Iterable getPackageDescriptors() { + return packageDescriptorMap.values(); + } + + @Override + public Iterable getMappingFileDescriptors() { + return mappingFileDescriptors; + } + }; + } + + private MappingFileDescriptor buildMappingFileDescriptor( + String name, + BootstrapServiceRegistry bootstrapServiceRegistry) { + final URL url = bootstrapServiceRegistry.getService( ClassLoaderService.class ).locateResource( name ); + if ( url == null ) { + throw persistenceException( "Unable to resolve named mapping-file [" + name + "]" ); + } + + return new MappingFileDescriptorImpl( name, new UrlInputStreamAccess( url ) ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // temporary! + @SuppressWarnings("unchecked") + public Map getConfigurationValues() { + return Collections.unmodifiableMap( configurationValues ); + } + + public Configuration getHibernateConfiguration() { + return hibernateConfiguration; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + @SuppressWarnings("unchecked") + private MetadataSources prepareMetadataSources( + IndexView jandexIndex, + DeploymentResources deploymentResources, + BootstrapServiceRegistry bootstrapServiceRegistry) { + // todo : this needs to tie into the metamodel branch... + MetadataSources metadataSources = new MetadataSources(); + + for ( ClassDescriptor classDescriptor : deploymentResources.getClassDescriptors() ) { + final String className = classDescriptor.getName(); + final ClassInfo classInfo = jandexIndex.getClassByName( DotName.createSimple( className ) ); + if ( classInfo == null ) { + // Not really sure what this means. Most likely it is explicitly listed in the persistence unit, + // but mapped via mapping file. Anyway assume its a mapping class... + metadataSources.annotatedMappingClassNames.add( className ); + continue; + } + + // logic here assumes an entity is not also a converter... + AnnotationInstance converterAnnotation = JandexHelper.getSingleAnnotation( + classInfo.annotations(), + JPADotNames.CONVERTER + ); + if ( converterAnnotation != null ) { + metadataSources.converterDescriptors.add( + new MetadataSources.ConverterDescriptor( + className, + JandexHelper.getValue( converterAnnotation, "autoApply", boolean.class ) + ) + ); + } + else { + metadataSources.annotatedMappingClassNames.add( className ); + } + } + + for ( PackageDescriptor packageDescriptor : deploymentResources.getPackageDescriptors() ) { + metadataSources.packageNames.add( packageDescriptor.getName() ); + } + + for ( MappingFileDescriptor mappingFileDescriptor : deploymentResources.getMappingFileDescriptors() ) { + metadataSources.namedMappingFileInputStreams.add( mappingFileDescriptor.getStreamAccess().asNamedInputStream() ); + } + + final String explicitHbmXmls = (String) configurationValues.remove( AvailableSettings.HBXML_FILES ); + if ( explicitHbmXmls != null ) { + metadataSources.mappingFileResources.addAll( Arrays.asList( StringHelper.split( ", ", explicitHbmXmls ) ) ); + } + + final List explicitOrmXml = (List) configurationValues.remove( AvailableSettings.XML_FILE_NAMES ); + if ( explicitOrmXml != null ) { + metadataSources.mappingFileResources.addAll( explicitOrmXml ); + } + + return metadataSources; + } + + private IndexView locateOrBuildJandexIndex(DeploymentResources deploymentResources) { + // for now create a whole new Index to work with, eventually we need to: + // 1) accept an Index as an incoming config value + // 2) pass that Index along to the metamodel code... + IndexView jandexIndex = (IndexView) configurationValues.get( JANDEX_INDEX ); + if ( jandexIndex == null ) { + jandexIndex = buildJandexIndex( deploymentResources ); + } + return jandexIndex; + } + + private IndexView buildJandexIndex(DeploymentResources deploymentResources) { + Indexer indexer = new Indexer(); + + for ( ClassDescriptor classDescriptor : deploymentResources.getClassDescriptors() ) { + indexStream( indexer, classDescriptor.getStreamAccess() ); + } + + for ( PackageDescriptor packageDescriptor : deploymentResources.getPackageDescriptors() ) { + indexStream( indexer, packageDescriptor.getStreamAccess() ); + } + + // for now we just skip entities defined in (1) orm.xml files and (2) hbm.xml files. this part really needs + // metamodel branch... + + // for now, we also need to wrap this in a CompositeIndex until Jandex is updated to use a common interface + // between the 2... + return indexer.complete(); + } + + private void indexStream(Indexer indexer, InputStreamAccess streamAccess) { + try { + InputStream stream = streamAccess.accessInputStream(); + try { + indexer.index( stream ); + } + finally { + try { + stream.close(); + } + catch (Exception ignore) { + } + } + } + catch ( IOException e ) { + throw persistenceException( "Unable to index from stream " + streamAccess.getStreamName(), e ); + } + } + /** * Builds the {@link BootstrapServiceRegistry} used to eventually build the {@link org.hibernate.boot.registry.StandardServiceRegistryBuilder}; mainly * used here during instantiation to define class-loading behavior. @@ -360,7 +601,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil = (JaxbHibernateConfiguration.JaxbSessionFactory.JaxbClassCache) cacheDeclaration; cacheRegionDefinitions.add( new CacheRegionDefinition( - CacheRegionDefinition.CacheRegionType.ENTITY, + CacheRegionDefinition.CacheType.ENTITY, jaxbClassCache.getClazz(), jaxbClassCache.getUsage().value(), jaxbClassCache.getRegion(), @@ -373,7 +614,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil = (JaxbHibernateConfiguration.JaxbSessionFactory.JaxbCollectionCache) cacheDeclaration; cacheRegionDefinitions.add( new CacheRegionDefinition( - CacheRegionDefinition.CacheRegionType.COLLECTION, + CacheRegionDefinition.CacheType.COLLECTION, jaxbCollectionCache.getCollection(), jaxbCollectionCache.getUsage().value(), jaxbCollectionCache.getRegion(), @@ -398,98 +639,6 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // temporary! - @SuppressWarnings("unchecked") - public Map getConfigurationValues() { - return Collections.unmodifiableMap( configurationValues ); - } - - public Configuration getHibernateConfiguration() { - return hibernateConfiguration; - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - @SuppressWarnings("unchecked") - private MetadataSources prepareMetadataSources( - IndexView jandexIndex, - Set collectedManagedClassNames, - ScanResult scanResult, - BootstrapServiceRegistry bootstrapServiceRegistry) { - // todo : this needs to tie into the metamodel branch... - MetadataSources metadataSources = new MetadataSources(); - - for ( String className : collectedManagedClassNames ) { - final ClassInfo classInfo = jandexIndex.getClassByName( DotName.createSimple( className ) ); - if ( classInfo == null ) { - // Not really sure what this means. Most likely it is explicitly listed in the persistence unit, - // but mapped via mapping file. Anyway assume its a mapping class... - metadataSources.annotatedMappingClassNames.add( className ); - continue; - } - - // logic here assumes an entity is not also a converter... - AnnotationInstance converterAnnotation = JandexHelper.getSingleAnnotation( - classInfo.annotations(), - JPADotNames.CONVERTER - ); - if ( converterAnnotation != null ) { - metadataSources.converterDescriptors.add( - new MetadataSources.ConverterDescriptor( - className, - JandexHelper.getValue( converterAnnotation, "autoApply", boolean.class ) - ) - ); - } - else { - metadataSources.annotatedMappingClassNames.add( className ); - } - } - - metadataSources.packageNames.addAll( scanResult.getPackageNames() ); - - metadataSources.namedMappingFileInputStreams.addAll( scanResult.getHbmFiles() ); - - metadataSources.mappingFileResources.addAll( scanResult.getMappingFiles() ); - final String explicitHbmXmls = (String) configurationValues.remove( AvailableSettings.HBXML_FILES ); - if ( explicitHbmXmls != null ) { - metadataSources.mappingFileResources.addAll( Arrays.asList( StringHelper.split( ", ", explicitHbmXmls ) ) ); - } - final List explicitOrmXml = (List) configurationValues.remove( AvailableSettings.XML_FILE_NAMES ); - if ( explicitOrmXml != null ) { - metadataSources.mappingFileResources.addAll( explicitOrmXml ); - } - - return metadataSources; - } - - private Set collectManagedClassNames(ScanResult scanResult) { - Set collectedNames = new HashSet(); - if ( persistenceUnit.getManagedClassNames() != null ) { - collectedNames.addAll( persistenceUnit.getManagedClassNames() ); - } - collectedNames.addAll( scanResult.getManagedClassNames() ); - return collectedNames; - } - - private IndexView locateOrBuildJandexIndex( - Set collectedManagedClassNames, - List packageNames, - BootstrapServiceRegistry bootstrapServiceRegistry) { - // for now create a whole new Index to work with, eventually we need to: - // 1) accept an Index as an incoming config value - // 2) pass that Index along to the metamodel code... - // - // (1) is mocked up here, but JBoss AS does not currently pass in any Index to use... - IndexView jandexIndex = (IndexView) configurationValues.get( JANDEX_INDEX ); - if ( jandexIndex == null ) { - jandexIndex = buildJandexIndex( collectedManagedClassNames, packageNames, bootstrapServiceRegistry ); - } - return jandexIndex; - } - private IndexView buildJandexIndex(Set classNamesSource, List packageNames, BootstrapServiceRegistry bootstrapServiceRegistry) { Indexer indexer = new Indexer(); @@ -551,11 +700,11 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } } - private void addCacheRegionDefinition(String role, String value, CacheRegionDefinition.CacheRegionType cacheType) { + private void addCacheRegionDefinition(String role, String value, CacheRegionDefinition.CacheType cacheType) { final StringTokenizer params = new StringTokenizer( value, ";, " ); if ( !params.hasMoreTokens() ) { StringBuilder error = new StringBuilder( "Illegal usage of " ); - if ( cacheType == CacheRegionDefinition.CacheRegionType.ENTITY ) { + if ( cacheType == CacheRegionDefinition.CacheType.ENTITY ) { error.append( AvailableSettings.CLASS_CACHE_PREFIX ) .append( ": " ) .append( AvailableSettings.CLASS_CACHE_PREFIX ); @@ -579,7 +728,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil region = params.nextToken(); } boolean lazyProperty = true; - if ( cacheType == CacheRegionDefinition.CacheRegionType.ENTITY ) { + if ( cacheType == CacheRegionDefinition.CacheType.ENTITY ) { if ( params.hasMoreTokens() ) { lazyProperty = "all".equalsIgnoreCase( params.nextToken() ); } @@ -594,37 +743,24 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil @SuppressWarnings("unchecked") private ScanResult scan(BootstrapServiceRegistry bootstrapServiceRegistry) { - Scanner scanner = locateOrBuildScanner( bootstrapServiceRegistry ); - ScanningContext scanningContext = new ScanningContext(); + final Scanner scanner = locateOrBuildScanner( bootstrapServiceRegistry ); + final ScanOptions scanOptions = determineScanOptions(); - final ScanResult scanResult = new ScanResult(); - if ( persistenceUnit.getMappingFileNames() != null ) { - scanResult.getMappingFiles().addAll( persistenceUnit.getMappingFileNames() ); - } + return scanner.scan( persistenceUnit, scanOptions ); + } - // dunno, but the old code did it... - scanningContext.setSearchOrm( ! scanResult.getMappingFiles().contains( META_INF_ORM_XML ) ); - - if ( persistenceUnit.getJarFileUrls() != null ) { - prepareAutoDetectionSettings( scanningContext, false ); - for ( URL jar : persistenceUnit.getJarFileUrls() ) { - scanningContext.setUrl( jar ); - scanInContext( scanner, scanningContext, scanResult ); - } - } - - prepareAutoDetectionSettings( scanningContext, persistenceUnit.isExcludeUnlistedClasses() ); - scanningContext.setUrl( persistenceUnit.getPersistenceUnitRootUrl() ); - scanInContext( scanner, scanningContext, scanResult ); - - return scanResult; + private ScanOptions determineScanOptions() { + return new StandardScanOptions( + (String) configurationValues.get( AvailableSettings.AUTODETECTION ), + persistenceUnit.isExcludeUnlistedClasses() + ); } @SuppressWarnings("unchecked") private Scanner locateOrBuildScanner(BootstrapServiceRegistry bootstrapServiceRegistry) { final Object value = configurationValues.remove( AvailableSettings.SCANNER ); if ( value == null ) { - return new NativeScanner(); + return new StandardScanner(); } if ( Scanner.class.isInstance( value ) ) { @@ -660,91 +796,6 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } } - private void prepareAutoDetectionSettings(ScanningContext context, boolean excludeUnlistedClasses) { - final String detectionSetting = (String) configurationValues.get( AvailableSettings.AUTODETECTION ); - - if ( detectionSetting == null ) { - if ( excludeUnlistedClasses ) { - context.setDetectClasses( false ); - context.setDetectHbmFiles( false ); - } - else { - context.setDetectClasses( true ); - context.setDetectHbmFiles( true ); - } - } - else { - for ( String token : StringHelper.split( ", ", detectionSetting ) ) { - if ( "class".equalsIgnoreCase( token ) ) { - context.setDetectClasses( true ); - } - if ( "hbm".equalsIgnoreCase( token ) ) { - context.setDetectClasses( true ); - } - } - } - } - - private void scanInContext( - Scanner scanner, - ScanningContext scanningContext, - ScanResult scanResult) { - if ( scanningContext.getUrl() == null ) { - // not sure i like just ignoring this being null, but this is exactly what the old code does... - LOG.containerProvidingNullPersistenceUnitRootUrl(); - return; - } - if ( scanningContext.getUrl().getProtocol().equalsIgnoreCase( "bundle" ) ) { - // TODO: Is there a way to scan the root bundle URL in OSGi containers? - // Although the URL provides a stream handler that works for finding - // resources in a specific Bundle, the root one does not work. - return; - } - - try { - if ( scanningContext.isDetectClasses() ) { - Set matchingPackages = scanner.getPackagesInJar( scanningContext.url, new HashSet>(0) ); - for ( Package pkg : matchingPackages ) { - scanResult.getPackageNames().add( pkg.getName() ); - } - - Set> annotationsToLookFor = new HashSet>(); - annotationsToLookFor.add( Entity.class ); - annotationsToLookFor.add( MappedSuperclass.class ); - annotationsToLookFor.add( Embeddable.class ); - annotationsToLookFor.add( Converter.class ); - Set> matchingClasses = scanner.getClassesInJar( scanningContext.url, annotationsToLookFor ); - for ( Class clazz : matchingClasses ) { - scanResult.getManagedClassNames().add( clazz.getName() ); - } - } - - Set patterns = new HashSet(); - if ( scanningContext.isSearchOrm() ) { - patterns.add( META_INF_ORM_XML ); - } - if ( scanningContext.isDetectHbmFiles() ) { - patterns.add( "**/*.hbm.xml" ); - } - if ( ! scanResult.getMappingFiles().isEmpty() ) { - patterns.addAll( scanResult.getMappingFiles() ); - } - if ( patterns.size() != 0 ) { - Set files = scanner.getFilesInJar( scanningContext.getUrl(), patterns ); - for ( NamedInputStream file : files ) { - scanResult.getHbmFiles().add( file ); - scanResult.getMappingFiles().remove( file.getName() ); - } - } - } - catch (PersistenceException e ) { - throw e; - } - catch ( RuntimeException e ) { - throw persistenceException( "error trying to scan url: " + scanningContext.getUrl().toString(), e ); - } - } - @Override public EntityManagerFactoryBuilder withValidatorFactory(Object validatorFactory) { this.validatorFactory = validatorFactory; @@ -886,14 +937,14 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil addCacheRegionDefinition( keyString.substring( AvailableSettings.CLASS_CACHE_PREFIX.length() + 1 ), (String) entry.getValue(), - CacheRegionDefinition.CacheRegionType.ENTITY + CacheRegionDefinition.CacheType.ENTITY ); } else if ( keyString.startsWith( AvailableSettings.COLLECTION_CACHE_PREFIX ) ) { addCacheRegionDefinition( keyString.substring( AvailableSettings.COLLECTION_CACHE_PREFIX.length() + 1 ), (String) entry.getValue(), - CacheRegionDefinition.CacheRegionType.COLLECTION + CacheRegionDefinition.CacheType.COLLECTION ); } else if ( keyString.startsWith( AvailableSettings.JACC_PREFIX ) @@ -1006,30 +1057,30 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil if ( jaccDefinitions != null ) { for ( JaccDefinition jaccDefinition : jaccDefinitions ) { - JACCConfiguration jaccCfg = new JACCConfiguration( jaccDefinition.getContextId() ); + JACCConfiguration jaccCfg = new JACCConfiguration( jaccDefinition.contextId ); jaccCfg.addPermission( - jaccDefinition.getRole(), - jaccDefinition.getClazz(), - jaccDefinition.getActions() + jaccDefinition.role, + jaccDefinition.clazz, + jaccDefinition.actions ); } } if ( cacheRegionDefinitions != null ) { for ( CacheRegionDefinition cacheRegionDefinition : cacheRegionDefinitions ) { - if ( cacheRegionDefinition.getRegionType() == CacheRegionDefinition.CacheRegionType.ENTITY ) { + if ( cacheRegionDefinition.cacheType == CacheRegionDefinition.CacheType.ENTITY ) { cfg.setCacheConcurrencyStrategy( - cacheRegionDefinition.getRole(), - cacheRegionDefinition.getUsage(), - cacheRegionDefinition.getRegion(), - cacheRegionDefinition.isCacheLazy() + cacheRegionDefinition.role, + cacheRegionDefinition.usage, + cacheRegionDefinition.region, + cacheRegionDefinition.cacheLazy ); } else { cfg.setCollectionCacheConcurrencyStrategy( - cacheRegionDefinition.getRole(), - cacheRegionDefinition.getUsage(), - cacheRegionDefinition.getRegion() + cacheRegionDefinition.role, + cacheRegionDefinition.usage, + cacheRegionDefinition.region ); } } @@ -1116,14 +1167,28 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil //addInputStream has the responsibility to close the stream cfg.addInputStream( new BufferedInputStream( namedInputStream.getStream() ) ); } - catch (MappingException me) { - //try our best to give the file name - if ( StringHelper.isEmpty( namedInputStream.getName() ) ) { - throw me; + catch ( InvalidMappingException e ) { + // try our best to give the file name + if ( StringHelper.isNotEmpty( namedInputStream.getName() ) ) { + throw new InvalidMappingException( + "Error while parsing file: " + namedInputStream.getName(), + e.getType(), + e.getPath(), + e + ); } else { + throw e; + } + } + catch (MappingException me) { + // try our best to give the file name + if ( StringHelper.isNotEmpty( namedInputStream.getName() ) ) { throw new MappingException("Error while parsing file: " + namedInputStream.getName(), me ); } + else { + throw me; + } } } for ( String packageName : metadataSources.packageNames ) { @@ -1162,65 +1227,39 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil return "[PersistenceUnit: " + persistenceUnit.getName() + "] "; } - public static class ScanningContext { - private URL url; - private boolean detectClasses; - private boolean detectHbmFiles; - private boolean searchOrm; + public static class CacheRegionDefinition { + public static enum CacheType { ENTITY, COLLECTION } - public URL getUrl() { - return url; - } + public final CacheType cacheType; + public final String role; + public final String usage; + public final String region; + public final boolean cacheLazy; - public void setUrl(URL url) { - this.url = url; - } - - public boolean isDetectClasses() { - return detectClasses; - } - - public void setDetectClasses(boolean detectClasses) { - this.detectClasses = detectClasses; - } - - public boolean isDetectHbmFiles() { - return detectHbmFiles; - } - - public void setDetectHbmFiles(boolean detectHbmFiles) { - this.detectHbmFiles = detectHbmFiles; - } - - public boolean isSearchOrm() { - return searchOrm; - } - - public void setSearchOrm(boolean searchOrm) { - this.searchOrm = searchOrm; + public CacheRegionDefinition( + CacheType cacheType, + String role, + String usage, + String region, boolean cacheLazy) { + this.cacheType = cacheType; + this.role = role; + this.usage = usage; + this.region = region; + this.cacheLazy = cacheLazy; } } - private static class ScanResult { - private final List managedClassNames = new ArrayList(); - private final List packageNames = new ArrayList(); - private final List hbmFiles = new ArrayList(); - private final List mappingFiles = new ArrayList(); + public static class JaccDefinition { + public final String contextId; + public final String role; + public final String clazz; + public final String actions; - public List getManagedClassNames() { - return managedClassNames; - } - - public List getPackageNames() { - return packageNames; - } - - public List getHbmFiles() { - return hbmFiles; - } - - public List getMappingFiles() { - return mappingFiles; + public JaccDefinition(String contextId, String role, String clazz, String actions) { + this.contextId = contextId; + this.role = role; + this.clazz = clazz; + this.actions = actions; } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/FileInputStreamAccess.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/FileInputStreamAccess.java new file mode 100644 index 0000000000..7d9294364a --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/FileInputStreamAccess.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.internal; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.hibernate.HibernateException; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.NamedInputStream; + +/** + * An InputStreamAccess implementation based on a File reference + * + * @author Steve Ebersole + */ +public class FileInputStreamAccess implements InputStreamAccess { + private final String name; + private final File file; + + public FileInputStreamAccess(String name, File file) { + this.name = name; + this.file = file; + if ( ! file.exists() ) { + throw new HibernateException( "File must exist : " + file.getAbsolutePath() ); + } + } + + @Override + public String getStreamName() { + return name; + } + + @Override + public InputStream accessInputStream() { + try { + return new BufferedInputStream( new FileInputStream( file ) ); + } + catch (FileNotFoundException e) { + // should never ever ever happen, but... + throw new ArchiveException( + "File believed to exist based on File.exists threw error when passed to FileInputStream ctor", + e + ); + } + } + + @Override + public NamedInputStream asNamedInputStream() { + return new NamedInputStream( getStreamName(), accessInputStream() ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/MappingFileDescriptorImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/MappingFileDescriptorImpl.java new file mode 100644 index 0000000000..1a073c4002 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/MappingFileDescriptorImpl.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.internal; + +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; + +/** + * @author Steve Ebersole + */ +public class MappingFileDescriptorImpl implements MappingFileDescriptor { + private final String name; + private final InputStreamAccess streamAccess; + + public MappingFileDescriptorImpl(String name, InputStreamAccess streamAccess) { + this.name = name; + this.streamAccess = streamAccess; + } + + @Override + public String getName() { + return name; + } + + @Override + public InputStreamAccess getStreamAccess() { + return streamAccess; + } + +// @Override +// public boolean equals(Object o) { +// if ( this == o ) { +// return true; +// } +// if ( o == null || getClass() != o.getClass() ) { +// return false; +// } +// +// MappingFileDescriptorImpl that = (MappingFileDescriptorImpl) o; +// +// return name.equals( that.name ) +// && streamAccess.getStreamName().equals( that.streamAccess.getStreamName() ); +// +// } +// +// @Override +// public int hashCode() { +// int result = name.hashCode(); +// result = 31 * result + streamAccess.getStreamName().hashCode(); +// return result; +// } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PackageDescriptorImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PackageDescriptorImpl.java new file mode 100644 index 0000000000..c40eb48786 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PackageDescriptorImpl.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.internal; + +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.PackageDescriptor; + +/** + * @author Steve Ebersole + */ +public class PackageDescriptorImpl implements PackageDescriptor { + private final String name; + private final InputStreamAccess streamAccess; + + public PackageDescriptorImpl(String name, InputStreamAccess streamAccess) { + this.name = name; + this.streamAccess = streamAccess; + } + + @Override + public String getName() { + return name; + } + + @Override + public InputStreamAccess getStreamAccess() { + return streamAccess; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PackageDescriptorImpl that = (PackageDescriptorImpl) o; + return name.equals( that.name ); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java index d593a4f191..0e89e3d937 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java @@ -54,8 +54,8 @@ import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.boot.archive.internal.ArchiveHelper; import org.hibernate.jpa.internal.EntityManagerMessageLogger; -import org.hibernate.jpa.packaging.internal.JarVisitorFactory; import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; @@ -121,7 +121,7 @@ public class PersistenceXmlParser { final Element element = (Element) children.item( i ); final String tag = element.getTagName(); if ( tag.equals( "persistence-unit" ) ) { - final URL puRootUrl = JarVisitorFactory.getJarURLFromURLEntry( xmlUrl, "/META-INF/persistence.xml" ); + final URL puRootUrl = ArchiveHelper.getJarURLFromURLEntry( xmlUrl, "/META-INF/persistence.xml" ); ParsedPersistenceXmlDescriptor persistenceUnit = new ParsedPersistenceXmlDescriptor( puRootUrl ); bindPersistenceUnit( persistenceUnit, element ); @@ -214,7 +214,7 @@ public class PersistenceXmlParser { persistenceUnit.addMappingFiles( extractContent( element ) ); } else if ( tag.equals( "jar-file" ) ) { - persistenceUnit.addJarFileUrl( JarVisitorFactory.getURLFromPath( extractContent( element ) ) ); + persistenceUnit.addJarFileUrl( ArchiveHelper.getURLFromPath( extractContent( element ) ) ); } else if ( tag.equals( "exclude-unlisted-classes" ) ) { persistenceUnit.setExcludeUnlistedClasses( true ); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/UrlInputStreamAccess.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/UrlInputStreamAccess.java new file mode 100644 index 0000000000..4281201381 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/UrlInputStreamAccess.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.internal; + +import java.io.InputStream; +import java.net.URL; + +import org.hibernate.HibernateException; +import org.hibernate.jpa.boot.spi.InputStreamAccess; +import org.hibernate.jpa.boot.spi.NamedInputStream; + +/** + * @author Steve Ebersole + */ +public class UrlInputStreamAccess implements InputStreamAccess { + private final URL url; + + public UrlInputStreamAccess(URL url) { + this.url = url; + } + + @Override + public String getStreamName() { + return url.toExternalForm(); + } + + @Override + public InputStream accessInputStream() { + try { + return url.openStream(); + } + catch (Exception e) { + throw new HibernateException( "Could not open url stream : " + url.toExternalForm() ); + } + } + + @Override + public NamedInputStream asNamedInputStream() { + return new NamedInputStream( getStreamName(), accessInputStream() ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanOptions.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanOptions.java new file mode 100644 index 0000000000..e2dcf9e154 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanOptions.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.internal; + +import org.hibernate.jpa.boot.scan.spi.ScanOptions; + +/** + * @author Steve Ebersole + */ +public class StandardScanOptions implements ScanOptions { + private final boolean detectClassesInRoot; + private final boolean detectClassesInNonRoot; + private final boolean detectHibernateMappingFiles; + + public StandardScanOptions() { + this( "hbm,class", false ); + } + + public StandardScanOptions(String explicitDetectionSetting, boolean persistenceUnitExcludeUnlistedClassesValue) { + if ( explicitDetectionSetting == null ) { + detectHibernateMappingFiles = true; + detectClassesInRoot = ! persistenceUnitExcludeUnlistedClassesValue; + detectClassesInNonRoot = true; + } + else { + detectHibernateMappingFiles = explicitDetectionSetting.contains( "hbm" ); + detectClassesInRoot = explicitDetectionSetting.contains( "class" ); + detectClassesInNonRoot = detectClassesInRoot; + } + } + + @Override + public boolean canDetectUnlistedClassesInRoot() { + return detectClassesInRoot; + } + + @Override + public boolean canDetectUnlistedClassesInNonRoot() { + return detectClassesInNonRoot; + } + + @Override + public boolean canDetectHibernateMappingFiles() { + return detectHibernateMappingFiles; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanner.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanner.java new file mode 100644 index 0000000000..6db4050a0e --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/internal/StandardScanner.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.internal; + +import org.hibernate.jpa.boot.archive.internal.StandardArchiveDescriptorFactory; +import org.hibernate.jpa.boot.scan.spi.AbstractScannerImpl; + +/** + * Standard implementation of the Scanner contract, supporting typical archive walking support where + * the urls we are processing can be treated using normal file handling. + * + * @author Steve Ebersole + * @author Emmanuel Bernard + */ +public class StandardScanner extends AbstractScannerImpl { + public StandardScanner() { + super( StandardArchiveDescriptorFactory.INSTANCE ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractJavaArtifactArchiveEntryHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractJavaArtifactArchiveEntryHandler.java new file mode 100644 index 0000000000..c8547ebf14 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractJavaArtifactArchiveEntryHandler.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntryHandler; + +/** + * Base class for commonality between handling class file entries and handling package-info file entries. + * + * @author Steve Ebersole + */ +public abstract class AbstractJavaArtifactArchiveEntryHandler implements ArchiveEntryHandler { + private final ScanOptions scanOptions; + + protected AbstractJavaArtifactArchiveEntryHandler(ScanOptions scanOptions) { + this.scanOptions = scanOptions; + } + + /** + * Check to see if the incoming name (class/package name) is either:
    + *
  • explicitly listed in a {@code } entry within the {@code }
  • + *
  • whether the scan options indicate that we are allowed to detect this entry
  • + *
+ * + * @param context Information about the archive. Mainly whether it is the root of the PU + * @param name The class/package name + * + * @return {@code true} if the named class/package is either detectable or explicitly listed; {@code false} + * otherwise. + */ + protected boolean isListedOrDetectable(ArchiveContext context, String name) { + // IMPL NOTE : protect the isExplicitlyListed call unless needed, since it can take time in a PU + // with lots of listed classes. The other conditions are simple boolean flag checks. + if ( context.isRootUrl() ) { + return scanOptions.canDetectUnlistedClassesInRoot() || isExplicitlyListed( context, name ); + } + else { + return scanOptions.canDetectUnlistedClassesInNonRoot() || isExplicitlyListed( context, name ); + } + } + + private boolean isExplicitlyListed(ArchiveContext context, String name) { + return context.getPersistenceUnitDescriptor().getManagedClassNames().contains( name ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractScannerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractScannerImpl.java new file mode 100644 index 0000000000..da5af94f25 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/AbstractScannerImpl.java @@ -0,0 +1,302 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptor; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntryHandler; +import org.hibernate.jpa.boot.internal.ClassDescriptorImpl; +import org.hibernate.jpa.boot.internal.MappingFileDescriptorImpl; +import org.hibernate.jpa.boot.internal.PackageDescriptorImpl; +import org.hibernate.jpa.boot.spi.ClassDescriptor; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; +import org.hibernate.jpa.boot.spi.PackageDescriptor; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractScannerImpl implements Scanner { + private final ArchiveDescriptorFactory archiveDescriptorFactory; + private final Map archiveDescriptorCache = new HashMap(); + + protected AbstractScannerImpl(ArchiveDescriptorFactory archiveDescriptorFactory) { + this.archiveDescriptorFactory = archiveDescriptorFactory; + } + + @Override + public ScanResult scan(PersistenceUnitDescriptor persistenceUnit, ScanOptions scanOptions) { + final ResultCollector resultCollector = new ResultCollector( scanOptions ); + + if ( persistenceUnit.getJarFileUrls() != null ) { + for ( URL url : persistenceUnit.getJarFileUrls() ) { + final ArchiveDescriptor descriptor = buildArchiveDescriptor( url, false, scanOptions ); + final ArchiveContext context = buildArchiveContext( persistenceUnit, false, resultCollector ); + descriptor.visitArchive( context ); + } + } + + if ( persistenceUnit.getPersistenceUnitRootUrl() != null ) { + final ArchiveDescriptor descriptor = buildArchiveDescriptor( persistenceUnit.getPersistenceUnitRootUrl(), true, scanOptions ); + final ArchiveContext context = buildArchiveContext( persistenceUnit, false, resultCollector ); + descriptor.visitArchive( context ); + } + + return ScanResultImpl.from( resultCollector ); + } + + private ArchiveContext buildArchiveContext( + PersistenceUnitDescriptor persistenceUnit, + boolean isRoot, + ArchiveEntryHandlers entryHandlers) { + return new ArchiveContextImpl( persistenceUnit, isRoot, entryHandlers ); + } + + protected static interface ArchiveEntryHandlers { + public ArchiveEntryHandler getClassFileHandler(); + public ArchiveEntryHandler getPackageInfoHandler(); + public ArchiveEntryHandler getFileHandler(); + } + + private ArchiveDescriptor buildArchiveDescriptor(URL url, boolean isRootUrl, ScanOptions scanOptions) { + final ArchiveDescriptor descriptor; + final ArchiveDescriptorInfo descriptorInfo = archiveDescriptorCache.get( url ); + if ( descriptorInfo == null ) { + descriptor = archiveDescriptorFactory.buildArchiveDescriptor( url ); + archiveDescriptorCache.put( + url, + new ArchiveDescriptorInfo( descriptor, isRootUrl, scanOptions ) + ); + } + else { + validateReuse( descriptorInfo, isRootUrl, scanOptions ); + descriptor = descriptorInfo.archiveDescriptor; + } + return descriptor; + } + + public static class ResultCollector + implements ArchiveEntryHandlers, + PackageInfoArchiveEntryHandler.Callback, + ClassFileArchiveEntryHandler.Callback, + NonClassFileArchiveEntryHandler.Callback { + private final ClassFileArchiveEntryHandler classFileHandler; + private final PackageInfoArchiveEntryHandler packageInfoHandler; + private final NonClassFileArchiveEntryHandler fileHandler; + + private final Set packageDescriptorSet = new HashSet(); + private final Set classDescriptorSet = new HashSet(); + private final Set mappingFileSet = new HashSet(); + + public ResultCollector(ScanOptions scanOptions) { + this.classFileHandler = new ClassFileArchiveEntryHandler( scanOptions, this ); + this.packageInfoHandler = new PackageInfoArchiveEntryHandler( scanOptions, this ); + this.fileHandler = new NonClassFileArchiveEntryHandler( scanOptions, this ); + } + + @Override + public ArchiveEntryHandler getClassFileHandler() { + return classFileHandler; + } + + @Override + public ArchiveEntryHandler getPackageInfoHandler() { + return packageInfoHandler; + } + + @Override + public ArchiveEntryHandler getFileHandler() { + return fileHandler; + } + + @Override + public void locatedPackage(PackageDescriptor packageDescriptor) { + if ( PackageDescriptorImpl.class.isInstance( packageDescriptor ) ) { + packageDescriptorSet.add( packageDescriptor ); + } + else { + // to make sure we have proper equals/hashcode + packageDescriptorSet.add( + new PackageDescriptorImpl( + packageDescriptor.getName(), + packageDescriptor.getStreamAccess() + ) + ); + } + } + + @Override + public void locatedClass(ClassDescriptor classDescriptor) { + if ( ClassDescriptorImpl.class.isInstance( classDescriptor ) ) { + classDescriptorSet.add( classDescriptor ); + } + else { + // to make sure we have proper equals/hashcode + classDescriptorSet.add( + new ClassDescriptorImpl( + classDescriptor.getName(), + classDescriptor.getStreamAccess() + ) + ); + } + } + + @Override + public void locatedMappingFile(MappingFileDescriptor mappingFileDescriptor) { + if ( MappingFileDescriptorImpl.class.isInstance( mappingFileDescriptor ) ) { + mappingFileSet.add( mappingFileDescriptor ); + } + else { + // to make sure we have proper equals/hashcode + mappingFileSet.add( + new MappingFileDescriptorImpl( + mappingFileDescriptor.getName(), + mappingFileDescriptor.getStreamAccess() + ) + ); + } + } + + public Set getPackageDescriptorSet() { + return packageDescriptorSet; + } + + public Set getClassDescriptorSet() { + return classDescriptorSet; + } + + public Set getMappingFileSet() { + return mappingFileSet; + } + } + + private static class ArchiveDescriptorInfo { + private final ArchiveDescriptor archiveDescriptor; + private final boolean isRoot; + private final ScanOptions scanOptions; + + private ArchiveDescriptorInfo( + ArchiveDescriptor archiveDescriptor, + boolean isRoot, + ScanOptions scanOptions) { + this.archiveDescriptor = archiveDescriptor; + this.isRoot = isRoot; + this.scanOptions = scanOptions; + } + } + + protected void validateReuse(ArchiveDescriptorInfo descriptor, boolean root, ScanOptions options) { + // is it really reasonable that a single url be processed multiple times? + // for now, throw an exception, mainly because I am interested in situations where this might happen + throw new IllegalStateException( "ArchiveDescriptor reused; can URLs be processed multiple times?" ); + } + + public static class ArchiveContextImpl implements ArchiveContext { + private final PersistenceUnitDescriptor persistenceUnitDescriptor; + private final boolean isRootUrl; + private final ArchiveEntryHandlers entryHandlers; + + public ArchiveContextImpl( + PersistenceUnitDescriptor persistenceUnitDescriptor, + boolean isRootUrl, + ArchiveEntryHandlers entryHandlers) { + this.persistenceUnitDescriptor = persistenceUnitDescriptor; + this.isRootUrl = isRootUrl; + this.entryHandlers = entryHandlers; + } + + @Override + public PersistenceUnitDescriptor getPersistenceUnitDescriptor() { + return persistenceUnitDescriptor; + } + + @Override + public boolean isRootUrl() { + return isRootUrl; + } + + @Override + public ArchiveEntryHandler obtainArchiveEntryHandler(ArchiveEntry entry) { + final String nameWithinArchive = entry.getNameWithinArchive(); + + if ( nameWithinArchive.endsWith( "package-info.class" ) ) { + return entryHandlers.getPackageInfoHandler(); + } + else if ( nameWithinArchive.endsWith( ".class" ) ) { + return entryHandlers.getClassFileHandler(); + } + else { + return entryHandlers.getFileHandler(); + } + } + } + + private static class ScanResultImpl implements ScanResult { + private final Set packageDescriptorSet; + private final Set classDescriptorSet; + private final Set mappingFileSet; + + private ScanResultImpl( + Set packageDescriptorSet, + Set classDescriptorSet, + Set mappingFileSet) { + this.packageDescriptorSet = packageDescriptorSet; + this.classDescriptorSet = classDescriptorSet; + this.mappingFileSet = mappingFileSet; + } + + private static ScanResult from(ResultCollector resultCollector) { + return new ScanResultImpl( + Collections.unmodifiableSet( resultCollector.packageDescriptorSet ), + Collections.unmodifiableSet( resultCollector.classDescriptorSet ), + Collections.unmodifiableSet( resultCollector.mappingFileSet ) + ); + } + + @Override + public Set getLocatedPackages() { + return packageDescriptorSet; + } + + @Override + public Set getLocatedClasses() { + return classDescriptorSet; + } + + @Override + public Set getLocatedMappingFiles() { + return mappingFileSet; + } + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ClassFileArchiveEntryHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ClassFileArchiveEntryHandler.java new file mode 100644 index 0000000000..33c61005b7 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ClassFileArchiveEntryHandler.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import javax.persistence.Converter; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.ClassFile; + +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveException; +import org.hibernate.jpa.boot.internal.ClassDescriptorImpl; +import org.hibernate.jpa.boot.spi.ClassDescriptor; + +/** + * Defines handling and filtering for class file entries within an archive + * + * @author Steve Ebersole + */ +public class ClassFileArchiveEntryHandler extends AbstractJavaArtifactArchiveEntryHandler { + private final Callback callback; + + /** + * Contract for the thing interested in being notified about accepted class descriptors. + */ + public static interface Callback { + public void locatedClass(ClassDescriptor classDescriptor); + } + + public ClassFileArchiveEntryHandler(ScanOptions scanOptions, Callback callback) { + super( scanOptions ); + this.callback = callback; + } + + @Override + public void handleEntry(ArchiveEntry entry, ArchiveContext context) { + final ClassFile classFile = toClassFile( entry ); + final ClassDescriptor classDescriptor = toClassDescriptor( classFile, entry ); + + if ( ! isListedOrDetectable( context, classDescriptor.getName() ) ) { + return; + } + + // we are only interested in classes with certain annotations, so see if the ClassDescriptor + // represents a class which contains any of those annotations + if ( ! containsClassAnnotationsOfInterest( classFile ) ) { + return; + } + + notifyMatchedClass( classDescriptor ); + } + + private ClassFile toClassFile(ArchiveEntry entry) { + final InputStream inputStream = entry.getStreamAccess().accessInputStream(); + final DataInputStream dataInputStream = new DataInputStream( inputStream ); + try { + return new ClassFile( dataInputStream ); + } + catch (IOException e) { + throw new ArchiveException( "Could not build ClassFile" ); + } + finally { + try { + dataInputStream.close(); + } + catch (Exception ignore) { + } + + try { + inputStream.close(); + } + catch (IOException ignore) { + } + } + } + + @SuppressWarnings("SimplifiableIfStatement") + private boolean containsClassAnnotationsOfInterest(ClassFile cf) { + final AnnotationsAttribute visibleAnnotations = (AnnotationsAttribute) cf.getAttribute( AnnotationsAttribute.visibleTag ); + if ( visibleAnnotations == null ) { + return false; + } + + return visibleAnnotations.getAnnotation( Entity.class.getName() ) != null + || visibleAnnotations.getAnnotation( MappedSuperclass.class.getName() ) != null + || visibleAnnotations.getAnnotation( Embeddable.class.getName() ) != null + || visibleAnnotations.getAnnotation( Converter.class.getName() ) != null; + } + + protected ClassDescriptor toClassDescriptor(ClassFile classFile, ArchiveEntry entry) { + return new ClassDescriptorImpl( classFile.getName(), entry.getStreamAccess() ); + } + + protected final void notifyMatchedClass(ClassDescriptor classDescriptor) { + callback.locatedClass( classDescriptor ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/NonClassFileArchiveEntryHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/NonClassFileArchiveEntryHandler.java new file mode 100644 index 0000000000..5e6fccb2fb --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/NonClassFileArchiveEntryHandler.java @@ -0,0 +1,87 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntryHandler; +import org.hibernate.jpa.boot.internal.MappingFileDescriptorImpl; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; + +/** + * Defines handling and filtering for all non-class file (package-info is also a class file...) entries within an archive + * + * @author Steve Ebersole + */ +public class NonClassFileArchiveEntryHandler implements ArchiveEntryHandler { + private final ScanOptions scanOptions; + private final Callback callback; + + /** + * Contract for the thing interested in being notified about accepted mapping file descriptors. + */ + public static interface Callback { + public void locatedMappingFile(MappingFileDescriptor mappingFileDescriptor); + } + + public NonClassFileArchiveEntryHandler(ScanOptions scanOptions, Callback callback) { + this.scanOptions = scanOptions; + this.callback = callback; + } + + @Override + public void handleEntry(ArchiveEntry entry, ArchiveContext context) { + if ( acceptAsMappingFile( entry, context) ) { + notifyMatchedMappingFile( entry ); + } + } + + @SuppressWarnings("SimplifiableIfStatement") + private boolean acceptAsMappingFile(ArchiveEntry entry, ArchiveContext context) { + if ( entry.getName().endsWith( "hbm.xml" ) ) { + return scanOptions.canDetectHibernateMappingFiles(); + } + + // todo : should really do this case-insensitively + if ( entry.getName().endsWith( "META-INF/orm.xml" ) ) { + if ( context.getPersistenceUnitDescriptor().getMappingFileNames().contains( "META-INF/orm.xml" ) ) { + // if the user explicitly listed META-INF/orm.xml, only except the root one + // + // not sure why exactly, but this is what the old code does + return context.isRootUrl(); + } + return true; + } + + return context.getPersistenceUnitDescriptor().getMappingFileNames().contains( entry.getNameWithinArchive() ); + } + + protected final void notifyMatchedMappingFile(ArchiveEntry entry) { + callback.locatedMappingFile( toMappingFileDescriptor( entry ) ); + } + + protected MappingFileDescriptor toMappingFileDescriptor(ArchiveEntry entry) { + return new MappingFileDescriptorImpl( entry.getNameWithinArchive(), entry.getStreamAccess() ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/PackageInfoArchiveEntryHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/PackageInfoArchiveEntryHandler.java new file mode 100644 index 0000000000..d824ef015e --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/PackageInfoArchiveEntryHandler.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import org.hibernate.jpa.boot.archive.spi.ArchiveContext; +import org.hibernate.jpa.boot.archive.spi.ArchiveEntry; +import org.hibernate.jpa.boot.internal.PackageDescriptorImpl; +import org.hibernate.jpa.boot.spi.PackageDescriptor; + +import static java.io.File.separatorChar; + +/** + * Defines handling and filtering for package-info file entries within an archive + * + * @author Steve Ebersole + */ +public class PackageInfoArchiveEntryHandler extends AbstractJavaArtifactArchiveEntryHandler { + private final Callback callback; + + /** + * Contract for the thing interested in being notified about accepted package-info descriptors. + */ + public static interface Callback { + public void locatedPackage(PackageDescriptor packageDescriptor); + } + + public PackageInfoArchiveEntryHandler(ScanOptions scanOptions, Callback callback) { + super( scanOptions ); + this.callback = callback; + } + + @Override + public void handleEntry(ArchiveEntry entry, ArchiveContext context) { + if ( entry.getNameWithinArchive().equals( "package-info.class" ) ) { + // the old code skipped package-info in the root package/dir... + return; + } + + if ( ! isListedOrDetectable( context, entry.getName() ) ) { + // the package is not explicitly listed, and we are not allowed to detect it. + return; + } + + notifyMatchedPackage( toPackageDescriptor( entry ) ); + } + + protected PackageDescriptor toPackageDescriptor(ArchiveEntry entry) { + final String packageInfoFilePath = entry.getNameWithinArchive(); + final String packageName = packageInfoFilePath.substring( 0, packageInfoFilePath.lastIndexOf( '/' ) ) + .replace( separatorChar, '.' ); + + return new PackageDescriptorImpl( packageName, entry.getStreamAccess() ); + } + + protected final void notifyMatchedPackage(PackageDescriptor packageDescriptor) { + callback.locatedPackage( packageDescriptor ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/PackageFilter.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanOptions.java similarity index 63% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/PackageFilter.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanOptions.java index d8ded44656..a5ec564a8d 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/PackageFilter.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanOptions.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,20 +21,14 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; - +package org.hibernate.jpa.boot.scan.spi; /** - * Filter on pachage element - * - * @author Emmanuel Bernard - * @see JavaElementFilter + * @author Steve Ebersole */ -public abstract class PackageFilter extends JavaElementFilter { - /** - * @see JavaElementFilter#JavaElementFilter(boolean, Class[]) - */ - protected PackageFilter(boolean retrieveStream, Class[] annotations) { - super( retrieveStream, annotations ); - } -} \ No newline at end of file +public interface ScanOptions { + public boolean canDetectUnlistedClassesInRoot(); + public boolean canDetectUnlistedClassesInNonRoot(); + + public boolean canDetectHibernateMappingFiles(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanResult.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanResult.java new file mode 100644 index 0000000000..13e0fa02f9 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/ScanResult.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.scan.spi; + +import java.util.Set; + +import org.hibernate.jpa.boot.spi.ClassDescriptor; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; +import org.hibernate.jpa.boot.spi.PackageDescriptor; + +/** + * Defines the result of scanning + * + * @author Steve Ebersole + */ +public interface ScanResult { + public Set getLocatedPackages(); + public Set getLocatedClasses(); + public Set getLocatedMappingFiles(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/Scanner.java similarity index 53% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitor.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/Scanner.java index cbb829ee3e..d57b54cb76 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarVisitor.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/scan/spi/Scanner.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -19,28 +21,26 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.internal; -import java.io.IOException; -import java.util.Set; +package org.hibernate.jpa.boot.scan.spi; + +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; /** + * Defines the contract for Hibernate to be able to scan for classes, packages and resources inside a + * persistence unit. + * * @author Emmanuel Bernard + * @author Steve Ebersole */ -public interface JarVisitor { +public interface Scanner { /** - * Get the unqualified Jar name (ie wo path and wo extension) + * Perform the scanning against the described persistence unit using the defined options, and return the scan + * results. * - * @return the unqualified jar name. - */ - String getUnqualifiedJarName(); - - Filter[] getFilters(); - - /** - * Return the matching entries for each filter in the same order the filter where passed + * @param persistenceUnit THe description of the persistence unit. + * @param options The scan options * - * @return array of Set of JarVisitor.Entry - * @throws java.io.IOException if something went wrong + * @return The scan results. */ - Set[] getMatchingEntries() throws IOException; + public ScanResult scan(PersistenceUnitDescriptor persistenceUnit, ScanOptions options); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ClassDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ClassDescriptor.java new file mode 100644 index 0000000000..c04e57d9c0 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ClassDescriptor.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.spi; + +/** + * Defines the result of scanning a persistence unit for classes. + * + * @author Steve Ebersole + */ +public interface ClassDescriptor { + public String getName(); + public InputStreamAccess getStreamAccess(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/InputStreamAccess.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/InputStreamAccess.java new file mode 100644 index 0000000000..cb51b1ada5 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/InputStreamAccess.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.spi; + +import java.io.InputStream; + +/** + * Contract for building InputStreams, especially in on-demand situations + * + * @author Steve Ebersole + */ +public interface InputStreamAccess { + /** + * Get the name of the resource backing the stream + * + * @return The backing resource name + */ + public String getStreamName(); + + /** + * Get access to the stream. Can be called multiple times, a different stream instance should be returned each time. + * + * @return The stream + */ + public InputStream accessInputStream(); + + /** + * @deprecated Needed until we can remove NamedInputStream + */ + public NamedInputStream asNamedInputStream(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/MappingFileDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/MappingFileDescriptor.java new file mode 100644 index 0000000000..5dfec7b9cb --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/MappingFileDescriptor.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.spi; + +/** + * @author Steve Ebersole + */ +public interface MappingFileDescriptor { + public String getName(); + public InputStreamAccess getStreamAccess(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/NamedInputStream.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/NamedInputStream.java similarity index 80% rename from hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/NamedInputStream.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/NamedInputStream.java index b0dfe020ac..fd6abed28f 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/NamedInputStream.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/NamedInputStream.java @@ -21,34 +21,33 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.jpa.packaging.spi; +package org.hibernate.jpa.boot.spi; + import java.io.InputStream; /** + * Bundles together a stream and the name that was used to locate it. The name is often useful for logging. + * + * @deprecated Use {@link org.hibernate.jpa.boot.spi.InputStreamAccess} instead. + * * @author Emmanuel Bernard + * @author Steve Ebersole */ +@Deprecated public class NamedInputStream { + private final String name; + private final InputStream stream; + public NamedInputStream(String name, InputStream stream) { this.name = name; this.stream = stream; } - private String name; - private InputStream stream; - public InputStream getStream() { return stream; } - public void setStream(InputStream stream) { - this.stream = stream; - } - public String getName() { return name; } - - public void setName(String name) { - this.name = name; - } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/PackageDescriptor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/PackageDescriptor.java new file mode 100644 index 0000000000..101e516575 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/PackageDescriptor.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.boot.spi; + +/** + * Defines the result of scanning a persistence unit for packages. + * + * @author Steve Ebersole + */ +public interface PackageDescriptor { + public String getName(); + public InputStreamAccess getStreamAccess(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/AbstractJarVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/AbstractJarVisitor.java deleted file mode 100644 index b857f9d237..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/AbstractJarVisitor.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javassist.bytecode.AnnotationsAttribute; -import javassist.bytecode.ClassFile; -import org.jboss.logging.Logger; - -import org.hibernate.jpa.internal.EntityManagerMessageLogger; - -/** - * Parse a JAR of any form (zip file, exploded directory, ...) - * apply a set of filters (File filter, Class filter, Package filter) - * and return the appropriate matching sets of elements - * - * @author Emmanuel Bernard - */ -public abstract class AbstractJarVisitor implements JarVisitor { - - //TODO shortcut when filters are null or empty - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - AbstractJarVisitor.class.getName()); - - protected String unqualifiedJarName; - protected URL jarUrl; - protected boolean done = false; - private List filters = new ArrayList(); - private Set fileFilters = new HashSet(); - private Set classFilters = new HashSet(); - private Set packageFilters = new HashSet(); - private Set[] entries; - - - - /** - * Build a jar visitor from its jar string path - */ - private AbstractJarVisitor(String jarPath) { - this.jarUrl = JarVisitorFactory.getURLFromPath( jarPath ); - unqualify(); - } - - protected AbstractJarVisitor(String fileName, Filter[] filters) { - this( fileName ); - initFilters( filters ); - } - - private void initFilters(Filter[] filters) { - for ( Filter filter : filters ) { - if ( filter instanceof FileFilter ) { - fileFilters.add( (FileFilter) filter ); - } - else if ( filter instanceof ClassFilter ) { - classFilters.add( (ClassFilter) filter ); - } - else if ( filter instanceof PackageFilter ) { - packageFilters.add( (PackageFilter) filter ); - } - else { - throw new AssertionError( "Unknown filter type: " + filter.getClass().getName() ); - } - this.filters.add( filter ); - } - int size = this.filters.size(); - this.entries = new Set[ size ]; - for ( int index = 0; index < size ; index++ ) { - this.entries[index] = new HashSet(); - } - } - - protected AbstractJarVisitor(URL url, Filter[] filters) { - this( url ); - initFilters( filters ); - } - - private AbstractJarVisitor(URL url) { - jarUrl = url; - unqualify(); - } - - protected void unqualify() { - //FIXME weak algorithm subject to AOOBE - String fileName = jarUrl.getFile(); - int exclamation = fileName.lastIndexOf( "!" ); - if (exclamation != -1) fileName = fileName.substring( 0, exclamation ); - int slash = fileName.lastIndexOf( "/" ); - if ( slash != -1 ) { - fileName = fileName.substring( - fileName.lastIndexOf( "/" ) + 1, - fileName.length() - ); - } - if ( fileName.length() > 4 && fileName.endsWith( "ar" ) && fileName.charAt( fileName.length() - 4 ) == '.' ) { - fileName = fileName.substring( 0, fileName.length() - 4 ); - } - unqualifiedJarName = fileName; - LOG.debugf("Searching mapped entities in jar/par: %s", jarUrl); - } - - /** - * Get the unqualified Jar name (ie wo path and wo extension) - */ - public String getUnqualifiedJarName() { - return unqualifiedJarName; - } - - public Filter[] getFilters() { - return filters.toArray( new Filter[ filters.size() ] ); - } - - /** - * Return the matching entries for each filter in the same order the filter where passed - * - * @return array of Set of JarVisitor.Entry - * @throws IOException if something went wrong - */ - public Set[] getMatchingEntries() throws IOException { - if ( !done ) { - //avoid url access and so on - if ( filters.size() > 0 ) doProcessElements(); - done = true; - } - return entries; - } - - protected abstract void doProcessElements() throws IOException; - - //TODO avoid 2 input stream when not needed - protected final void addElement(String entryName, InputStream is, InputStream secondIs) throws IOException { - int entryNameLength = entryName.length(); - if ( entryName.endsWith( "package-info.class" ) ) { - String name; - if ( entryNameLength == "package-info.class".length() ) { - name = ""; - } - else { - name = entryName.substring( 0, entryNameLength - ".package-info.class".length() ).replace( '/', '.' ); - } - executeJavaElementFilter( name, packageFilters, is, secondIs ); - } - else if ( entryName.endsWith( ".class" ) ) { - String name = entryName.substring( 0, entryNameLength - ".class".length() ).replace( '/', '.' ); - LOG.debugf("Filtering: %s", name); - executeJavaElementFilter( name, classFilters, is, secondIs ); - } - else { - String name = entryName; - boolean accepted = false; - for ( FileFilter filter : fileFilters ) { - if ( filter.accept( name ) ) { - accepted = true; - InputStream localIs; - if ( filter.getStream() ) { - localIs = secondIs; - } - else { - localIs = null; - secondIs.close(); - } - is.close(); - LOG.debugf("File Filter matched for %s", name); - Entry entry = new Entry( name, localIs ); - int index = this.filters.indexOf( filter ); - this.entries[index].add( entry ); - } - } - if (!accepted) { - //not accepted free resources - is.close(); - secondIs.close(); - } - } - } - - private void executeJavaElementFilter( - String name, Set filters, InputStream is, InputStream secondIs - ) throws IOException { - boolean accepted = false; - for ( JavaElementFilter filter : filters ) { - if ( filter.accept( name ) ) { - //FIXME cannot currently have a class filtered twice but matching once - // need to copy the is - boolean match = checkAnnotationMatching( is, filter ); - if ( match ) { - accepted = true; - InputStream localIs; - if ( filter.getStream() ) { - localIs = secondIs; - } - else { - localIs = null; - secondIs.close(); - } - LOG.debugf("Java element filter matched for %s", name); - Entry entry = new Entry( name, localIs ); - int index = this.filters.indexOf( filter ); - this.entries[index].add( entry ); - break; //we matched - } - } - } - if (!accepted) { - is.close(); - secondIs.close(); - } - } - - private boolean checkAnnotationMatching(InputStream is, JavaElementFilter filter) throws IOException { - if ( filter.getAnnotations().length == 0 ) { - is.close(); - return true; - } - DataInputStream dstream = new DataInputStream( is ); - ClassFile cf = null; - - try { - cf = new ClassFile( dstream ); - } - finally { - dstream.close(); - is.close(); - } - boolean match = false; - AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute( AnnotationsAttribute.visibleTag ); - if ( visible != null ) { - for ( Class annotation : filter.getAnnotations() ) { - match = visible.getAnnotation( annotation.getName() ) != null; - if ( match ) break; - } - } - return match; - } -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ExplodedJarVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ExplodedJarVisitor.java deleted file mode 100644 index cef0a7d9aa..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/ExplodedJarVisitor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Enumeration; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; - -import org.jboss.logging.Logger; - -import org.hibernate.jpa.internal.EntityManagerMessageLogger; - - -/** - * @author Emmanuel Bernard - */ -public class ExplodedJarVisitor extends AbstractJarVisitor { - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - ExplodedJarVisitor.class.getName()); - - private String entry; - - public ExplodedJarVisitor(URL url, Filter[] filters, String entry) { - super( url, filters ); - this.entry = entry; - } - - public ExplodedJarVisitor(String fileName, Filter[] filters) { - super( fileName, filters ); - } - - @Override - protected void doProcessElements() throws IOException { - File jarFile; - try { - String filePart = jarUrl.getFile(); - if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { - //unescaped (from the container), keep as is - jarFile = new File( jarUrl.getFile() ); - } - else { - jarFile = new File( jarUrl.toURI().getSchemeSpecificPart() ); - } - } - catch (URISyntaxException e) { - LOG.malformedUrl(jarUrl, e); - return; - } - - if ( !jarFile.exists() ) { - LOG.explodedJarDoesNotExist(jarUrl); - return; - } - if ( !jarFile.isDirectory() ) { - LOG.explodedJarNotDirectory(jarUrl); - return; - } - File rootFile; - if (entry != null && entry.length() > 0 && ! "/".equals( entry ) ) { - rootFile = new File(jarFile, entry); - } - else { - rootFile = jarFile; - } - if ( rootFile.isDirectory() ) { - getClassNamesInTree( rootFile, null ); - } - else { - //assume zipped file - processZippedRoot(rootFile); - } - } - - //FIXME shameful copy of FileZippedJarVisitor.doProcess() - //TODO long term fix is to introduce a process interface (closure like) to addElements and then share the code - private void processZippedRoot(File rootFile) throws IOException { - JarFile jarFile = new JarFile(rootFile); - Enumeration entries = jarFile.entries(); - while ( entries.hasMoreElements() ) { - ZipEntry zipEntry = entries.nextElement(); - String name = zipEntry.getName(); - if ( !zipEntry.isDirectory() ) { - //build relative name - if ( name.startsWith( "/" ) ) name = name.substring( 1 ); - addElement( - name, - new BufferedInputStream( jarFile.getInputStream( zipEntry ) ), - new BufferedInputStream( jarFile.getInputStream( zipEntry ) ) - ); - } - } - } - - private void getClassNamesInTree(File jarFile, String header) throws IOException { - File[] files = jarFile.listFiles(); - header = header == null ? "" : header + "/"; - for ( File localFile : files ) { - if ( !localFile.isDirectory() ) { - String entryName = localFile.getName(); - addElement( - header + entryName, - new BufferedInputStream( new FileInputStream( localFile ) ), - new BufferedInputStream( new FileInputStream( localFile ) ) - ); - - } - else { - getClassNamesInTree( localFile, header + localFile.getName() ); - } - } - } -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileZippedJarVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileZippedJarVisitor.java deleted file mode 100644 index 8dd003ce59..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/FileZippedJarVisitor.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Enumeration; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import java.util.zip.ZipEntry; - -import org.jboss.logging.Logger; - -import org.hibernate.jpa.internal.EntityManagerMessageLogger; - -/** - * Work on a JAR that can be accessed through a File - * - * @author Emmanuel Bernard - */ -public class FileZippedJarVisitor extends AbstractJarVisitor { - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - FileZippedJarVisitor.class.getName()); - - private String entry; - - public FileZippedJarVisitor(String fileName, Filter[] filters) { - super( fileName, filters ); - } - - public FileZippedJarVisitor(URL url, Filter[] filters, String entry) { - super( url, filters ); - this.entry = entry; - } - - @Override - protected void doProcessElements() throws IOException { - JarFile jarFile; - try { - String filePart = jarUrl.getFile(); - if ( filePart != null && filePart.indexOf( ' ' ) != -1 ) { - //unescaped (from the container), keep as is - jarFile = new JarFile( jarUrl.getFile() ); - } - else { - jarFile = new JarFile( jarUrl.toURI().getSchemeSpecificPart() ); - } - } - catch (IOException ze) { - LOG.unableToFindFile(jarUrl, ze); - return; - } - catch (URISyntaxException e) { - LOG.malformedUrlWarning(jarUrl, e); - return; - } - - if ( entry != null && entry.length() == 1 ) entry = null; //no entry - if ( entry != null && entry.startsWith( "/" ) ) entry = entry.substring( 1 ); //remove '/' header - - Enumeration entries = jarFile.entries(); - while ( entries.hasMoreElements() ) { - ZipEntry zipEntry = entries.nextElement(); - String name = zipEntry.getName(); - if ( entry != null && ! name.startsWith( entry ) ) continue; //filter it out - if ( !zipEntry.isDirectory() ) { - if ( name.equals( entry ) ) { - //exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) - /* - * This algorithm assumes that the zipped file is only the URL root (including entry), not just any random entry - */ - InputStream is = null; - try { - is = new BufferedInputStream( jarFile.getInputStream( zipEntry ) ); - JarInputStream jis = new JarInputStream( is ); - ZipEntry subZipEntry = jis.getNextEntry(); - while (subZipEntry != null) { - if ( ! subZipEntry.isDirectory() ) { - //FIXME copy sucks - byte[] entryBytes = JarVisitorFactory.getBytesFromInputStream( jis ); - String subname = subZipEntry.getName(); - if ( subname.startsWith( "/" ) ) subname = subname.substring( 1 ); - addElement( - subname, - new ByteArrayInputStream(entryBytes), - new ByteArrayInputStream(entryBytes) - ); - } - subZipEntry = jis.getNextEntry(); - } - } - finally { - if ( is != null) is.close(); - } - } - else { - //build relative name - if (entry != null) name = name.substring( entry.length() ); - if ( name.startsWith( "/" ) ) name = name.substring( 1 ); - addElement( - name, - new BufferedInputStream( jarFile.getInputStream( zipEntry ) ), - new BufferedInputStream( jarFile.getInputStream( zipEntry ) ) - ); - } - } - } - } -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/InputStreamZippedJarVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/InputStreamZippedJarVisitor.java deleted file mode 100644 index 27163d29ae..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/InputStreamZippedJarVisitor.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URL; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.zip.ZipEntry; - -import org.jboss.logging.Logger; - -import org.hibernate.jpa.internal.EntityManagerMessageLogger; - - -/** - * Work on a JAR that can only be accessed through a inputstream - * This is less efficient than the {@link FileZippedJarVisitor} - * - * @author Emmanuel Bernard - */ -public class InputStreamZippedJarVisitor extends AbstractJarVisitor { - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - InputStreamZippedJarVisitor.class.getName()); - - private String entry; - - public InputStreamZippedJarVisitor(URL url, Filter[] filters, String entry) { - super( url, filters ); - this.entry = entry; - } - - public InputStreamZippedJarVisitor(String fileName, Filter[] filters) { - super( fileName, filters ); - } - - @Override - protected void doProcessElements() throws IOException { - JarInputStream jis; - try { - jis = new JarInputStream( jarUrl.openStream() ); - } - catch (Exception ze) { - //really should catch IOException but Eclipse is buggy and raise NPE... - LOG.unableToFindFile(jarUrl, ze); - return; - } - if ( entry != null && entry.length() == 1 ) entry = null; //no entry - if ( entry != null && entry.startsWith( "/" ) ) entry = entry.substring( 1 ); //remove '/' header - - JarEntry jarEntry; - while ( ( jarEntry = jis.getNextJarEntry() ) != null ) { - String name = jarEntry.getName(); - if ( entry != null && ! name.startsWith( entry ) ) continue; //filter it out - if ( !jarEntry.isDirectory() ) { - if ( name.equals( entry ) ) { - //exact match, might be a nested jar entry (ie from jar:file:..../foo.ear!/bar.jar) - /* - * This algorithm assumes that the zipped file is only the URL root (including entry), not just any random entry - */ - JarInputStream subJis = null; - try { - subJis = new JarInputStream( jis ); - ZipEntry subZipEntry = jis.getNextEntry(); - while (subZipEntry != null) { - if ( ! subZipEntry.isDirectory() ) { - //FIXME copy sucks - byte[] entryBytes = JarVisitorFactory.getBytesFromInputStream( jis ); - String subname = subZipEntry.getName(); - if ( subname.startsWith( "/" ) ) subname = subname.substring( 1 ); - addElement( - subname, - new ByteArrayInputStream(entryBytes), - new ByteArrayInputStream(entryBytes) - ); - } - subZipEntry = jis.getNextJarEntry(); - } - } - finally { - if (subJis != null) subJis.close(); - } - } - else { - byte[] entryBytes = JarVisitorFactory.getBytesFromInputStream( jis ); - //build relative name - if (entry != null) name = name.substring( entry.length() ); - if ( name.startsWith( "/" ) ) name = name.substring( 1 ); - //this is bad cause we actually read everything instead of walking it lazily - addElement( - name, - new ByteArrayInputStream( entryBytes ), - new ByteArrayInputStream( entryBytes ) - ); - } - } - } - jis.close(); - } -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarProtocolVisitor.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarProtocolVisitor.java deleted file mode 100644 index c1bd24a169..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JarProtocolVisitor.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.IOException; -import java.net.URL; -import java.util.Set; - -import org.hibernate.annotations.common.AssertionFailure; - -/** - * @author Emmanuel Bernard - */ -public class JarProtocolVisitor implements JarVisitor { - private JarVisitor delegate; - private URL jarUrl; - private Filter[] filters; - - public JarProtocolVisitor(URL url, Filter[] filters, String entry) { - this.jarUrl = url; - this.filters = filters; - if (entry != null && entry.length() > 0) throw new IllegalArgumentException( "jar:jar: not supported: " + jarUrl ); - init(); - } - - private void init() { - String file = jarUrl.getFile(); - String entry; - int subEntryIndex = file.lastIndexOf( "!" ); - if (subEntryIndex == -1) throw new AssertionFailure("JAR URL does not contain '!/' :" + jarUrl); - if ( subEntryIndex + 1 >= file.length() ) { - entry = ""; - } - else { - entry = file.substring( subEntryIndex + 1 ); - } - URL fileUrl = JarVisitorFactory.getJarURLFromURLEntry( jarUrl, entry ); - delegate = JarVisitorFactory.getVisitor( fileUrl, filters, entry ); - - } - - public String getUnqualifiedJarName() { - return delegate.getUnqualifiedJarName(); - } - - public Filter[] getFilters() { - return delegate.getFilters(); - } - - public Set[] getMatchingEntries() throws IOException { - return delegate.getMatchingEntries(); - } - -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JavaElementFilter.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JavaElementFilter.java deleted file mode 100644 index ab7e42fc42..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/JavaElementFilter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - - -/** - * Filter a Java element (class or package per fully qualified name and annotation existence) - * At least 1 annotation has to annotate the element and the accept method must match - * If none annotations are passed, only the accept method must pass. - * - * @author Emmanuel Bernard - */ -public abstract class JavaElementFilter extends Filter { - private Class[] annotations; - - /** - * @param retrieveStream Give back an open stream to the matching element or not - * @param annotations Array of annotations that must be present to match (1 of them should annotate the element - */ - protected JavaElementFilter(boolean retrieveStream, Class[] annotations) { - super( retrieveStream ); - this.annotations = annotations == null ? new Class[]{} : annotations; - } - - public Class[] getAnnotations() { - return annotations; - } - - /** - * Return true if the fully qualified name match - */ - public abstract boolean accept(String javaElementName); -} \ No newline at end of file diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/NativeScanner.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/NativeScanner.java deleted file mode 100644 index 6a568a981d..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/internal/NativeScanner.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.internal; - -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.persistence.Converter; -import javax.persistence.Embeddable; -import javax.persistence.Entity; -import javax.persistence.MappedSuperclass; - -import org.hibernate.AssertionFailure; -import org.hibernate.internal.util.ReflectHelper; -import org.hibernate.jpa.packaging.spi.NamedInputStream; -import org.hibernate.jpa.packaging.spi.Scanner; - -/** - * @author Emmanuel Bernard - */ -public class NativeScanner implements Scanner { - - private static final String META_INF_ORM_XML = "META-INF/orm.xml"; - - private Map visitors = new HashMap(); - private static final int PACKAGE_FILTER_INDEX = 0; - private static final int CLASS_FILTER_INDEX = 1; - private static final int FILE_FILTER_INDEX = 2; - - /** - * This implementation does not honor the list of annotations and return everything. - * Must strictly be used by HEM - */ - public Set getPackagesInJar(URL jarToScan, Set> annotationsToLookFor) { - if ( annotationsToLookFor.size() > 0 ) { - throw new AssertionFailure( "Improper use of NativeScanner: must not filter packages" ); - } - - JarVisitor jarVisitor = getVisitor( jarToScan ); - final Set packageEntries; - try { - packageEntries = ( Set ) jarVisitor.getMatchingEntries()[PACKAGE_FILTER_INDEX]; - } - catch ( IOException e ) { - throw new RuntimeException( "Error while reading " + jarToScan.toString(), e ); - } - Set packages = new HashSet( packageEntries.size() ); - for ( Entry entry : packageEntries ) { - try { - packages.add( ReflectHelper.classForName( entry.getName() + ".package-info" ).getPackage() ); - } - catch ( ClassNotFoundException e ) { - //should never happen, if it happens, simply ignore the flawed package - } - } - return packages; - } - - /** - * Build a JarVisitor with some assumptions wrt the scanning - * This helps do one scan instead of several - */ - private JarVisitor getVisitor(URL jar) { - StateJarVisitor stateJarVisitor = visitors.get( jar ); - - if ( stateJarVisitor == null ) { - - Filter[] filters = new Filter[3]; - filters[PACKAGE_FILTER_INDEX] = new PackageFilter( false, null ) { - public boolean accept(String javaElementName) { - return true; - } - }; - filters[CLASS_FILTER_INDEX] = new ClassFilter( - false, new Class[] { - Entity.class, - MappedSuperclass.class, - Embeddable.class - } - ) { - public boolean accept(String javaElementName) { - return true; - } - }; - filters[FILE_FILTER_INDEX] = new FileFilter( true ) { - public boolean accept(String javaElementName) { - return javaElementName.endsWith( "hbm.xml" ) - || javaElementName.endsWith( META_INF_ORM_XML ); - } - }; - - stateJarVisitor = new StateJarVisitor( JarVisitorFactory.getVisitor( jar, filters ) ); - visitors.put( jar, stateJarVisitor ); - } - return stateJarVisitor.visitor; - } - - public Set> getClassesInJar(URL jarToScan, Set> annotationsToLookFor) { - if ( isValidForClasses( annotationsToLookFor ) ) { - throw new AssertionFailure( - "Improper use of NativeScanner: " - + "must not filter classes by other annotations than Entity, MappedSuperclass, embeddable" - ); - } - JarVisitor jarVisitor = getVisitor( jarToScan ); - final Set classesEntry; - try { - classesEntry = ( Set ) jarVisitor.getMatchingEntries()[CLASS_FILTER_INDEX]; - } - catch ( IOException e ) { - throw new RuntimeException( "Error while reading " + jarToScan.toString(), e ); - } - Set> classes = new HashSet>( classesEntry.size() ); - for ( Entry entry : classesEntry ) { - try { - classes.add( ReflectHelper.classForName( entry.getName() ) ); - } - catch ( ClassNotFoundException e ) { - //should never happen, if it happens, simply ignore the flawed package - } - } - return classes; - } - - private boolean isValidForClasses(Set> annotationsToLookFor) { - return annotationsToLookFor.size() != 4 - || !annotationsToLookFor.contains( Entity.class ) - || !annotationsToLookFor.contains( MappedSuperclass.class ) - || !annotationsToLookFor.contains( Embeddable.class ) - || !annotationsToLookFor.contains( Converter.class ); - } - - /** - * support for patterns is primitive: - * - **\/*.hbm.xml - * Other patterns will not be found - */ - public Set getFilesInJar(URL jarToScan, Set filePatterns) { - StringBuilder sb = new StringBuilder("URL: ").append( jarToScan ) - .append( "\n" ); - for (String pattern : filePatterns) { - sb.append( " " ).append( pattern ).append( "\n" ); - } - JarVisitor jarVisitor = getVisitor( jarToScan ); - - //state visitor available - final StateJarVisitor stateVisitor = visitors.get( jarToScan ); - if ( stateVisitor.hasReadFiles ) { - throw new AssertionFailure( "Cannot read files twice on NativeScanner" ); - } - stateVisitor.hasReadFiles = true; - - Set endWiths = new HashSet(); - Set exacts = new HashSet(); - for ( String pattern : filePatterns ) { - if ( pattern.startsWith( "**/*" ) ) { - final String patternTail = pattern.substring( 4, pattern.length() ); - if ( !patternTail.equals( ".hbm.xml" ) ) { - throw new AssertionFailure( - "Improper use of NativeScanner: " - + "must not filter files via pattern other than .hbm.xml" - ); - } - endWiths.add( patternTail ); - } - else { - exacts.add( pattern ); - } - } - - final Set fileEntries; - try { - fileEntries = ( Set ) jarVisitor.getMatchingEntries()[FILE_FILTER_INDEX]; - } - catch ( IOException e ) { - throw new RuntimeException( "Error while reading " + jarToScan.toString(), e ); - } - Set files = new HashSet( fileEntries.size() ); - Set leftOver = new HashSet( fileEntries ); - for ( Entry entry : fileEntries ) { - boolean done = false; - for ( String exact : exacts ) { - if ( entry.getName().equals( exact ) ) { - files.add( new NamedInputStream( entry.getName(), entry.getInputStream() ) ); - leftOver.remove( entry ); - done = true; - } - } - if (done) continue; - for ( String endWithPattern : endWiths ) { - if ( entry.getName().endsWith( endWithPattern ) ) { - files.add( new NamedInputStream( entry.getName(), entry.getInputStream() ) ); - leftOver.remove( entry ); - } - } - - } - for ( Entry entry : leftOver ) { - try { - entry.getInputStream().close(); - } - catch ( IOException e ) { - //swallow as we don't care about these files - } - } - return files; - } - - public Set getFilesInClasspath(Set filePatterns) { - throw new AssertionFailure( "Not implemented" ); - } - - public String getUnqualifiedJarName(URL jarToScan) { - JarVisitor jarVisitor = getVisitor( jarToScan ); - return jarVisitor.getUnqualifiedJarName(); - } - - private static class StateJarVisitor { - StateJarVisitor(JarVisitor visitor) { - this.visitor = visitor; - } - JarVisitor visitor; - boolean hasReadFiles = false; - } -} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/Scanner.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/Scanner.java deleted file mode 100644 index 241d233d61..0000000000 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/packaging/spi/Scanner.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.jpa.packaging.spi; -import java.lang.annotation.Annotation; -import java.net.URL; -import java.util.Set; - -/** - * @author Emmanuel Bernard - */ -public interface Scanner { - /** - * return all packages in the jar matching one of these annotations - * if annotationsToLookFor is empty, return all packages - */ - Set getPackagesInJar(URL jartoScan, Set> annotationsToLookFor); - - /** - * return all classes in the jar matching one of these annotations - * if annotationsToLookFor is empty, return all classes - */ - Set> getClassesInJar(URL jartoScan, Set> annotationsToLookFor); - - /** - * return all files in the jar matching one of these file names - * if filePatterns is empty, return all files - * eg **\/*.hbm.xml, META-INF/orm.xml - */ - Set getFilesInJar(URL jartoScan, Set filePatterns); - - - /** - * Return all files in the classpath (ie PU visibility) matching one of these file names - * if filePatterns is empty, return all files - * the use case is really exact file name. - * - * NOT USED by HEM at the moment. We use exact file search via getResourceAsStream for now. - */ - Set getFilesInClasspath(Set filePatterns); - - /** - * return the unqualified JAR name ie customer-model.jar or store.war - */ - String getUnqualifiedJarName(URL jarUrl); - -} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/TestHelper.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/TestHelper.java new file mode 100644 index 0000000000..90ef4d9ce8 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/TestHelper.java @@ -0,0 +1,67 @@ +package org.hibernate.jpa.test; + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import static java.io.File.separatorChar; + +/** + * @author Steve Ebersole + */ +public class TestHelper { + private static URL RESOLVED_TEST_ROOT_URL; + + public static URL determineTestRootUrl() { + if ( RESOLVED_TEST_ROOT_URL == null ) { + RESOLVED_TEST_ROOT_URL = resolveRootUrl( TestHelper.class ); + } + return RESOLVED_TEST_ROOT_URL; + } + + public static URL resolveRootUrl(Class knownClass) { + final String knownClassFileName = '/' + knownClass.getName().replace( '.', separatorChar ) + ".class"; + final URL knownClassFileUrl = TestHelper.class.getResource( knownClassFileName ); + final String knownClassFileUrlString = knownClassFileUrl.toExternalForm(); + + // to start, strip off the class file name + String rootUrlString = knownClassFileUrlString.substring( 0, knownClassFileUrlString.lastIndexOf( separatorChar ) ); + + // then strip off each package dir + final String packageName = knownClass.getPackage().getName(); + for ( String packageNamePart : packageName.split( "\\." ) ) { + rootUrlString = rootUrlString.substring( 0, rootUrlString.lastIndexOf( separatorChar ) ); + } + + try { + return new URL( rootUrlString ); + } + catch (MalformedURLException e) { + throw new RuntimeException( "Could not convert class base url as string to URL ref", e ); + } + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/CustomScanner.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/CustomScanner.java index c1ec0fdf84..71a6d5e72b 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/CustomScanner.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/CustomScanner.java @@ -1,19 +1,17 @@ package org.hibernate.jpa.test.packaging; -import java.lang.annotation.Annotation; -import java.net.URL; -import java.util.Set; - -import org.hibernate.jpa.packaging.internal.NativeScanner; -import org.hibernate.jpa.packaging.spi.NamedInputStream; -import org.hibernate.jpa.packaging.spi.Scanner; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.boot.scan.internal.StandardScanner; +import org.hibernate.jpa.boot.scan.spi.ScanOptions; +import org.hibernate.jpa.boot.scan.spi.ScanResult; +import org.hibernate.jpa.boot.scan.spi.Scanner; /** * @author Emmanuel Bernard */ public class CustomScanner implements Scanner { public static boolean isUsed = false; - private Scanner scanner = new NativeScanner(); + private Scanner delegate = new StandardScanner(); public static boolean isUsed() { return isUsed; @@ -23,28 +21,9 @@ public class CustomScanner implements Scanner { isUsed = false; } - public Set getPackagesInJar(URL jartoScan, Set> annotationsToLookFor) { + @Override + public ScanResult scan(PersistenceUnitDescriptor persistenceUnit, ScanOptions options) { isUsed = true; - return scanner.getPackagesInJar( jartoScan, annotationsToLookFor ); - } - - public Set> getClassesInJar(URL jartoScan, Set> annotationsToLookFor) { - isUsed = true; - return scanner.getClassesInJar( jartoScan, annotationsToLookFor ); - } - - public Set getFilesInJar(URL jartoScan, Set filePatterns) { - isUsed = true; - return scanner.getFilesInJar( jartoScan, filePatterns ); - } - - public Set getFilesInClasspath(Set filePatterns) { - isUsed = true; - return scanner.getFilesInClasspath( filePatterns ); - } - - public String getUnqualifiedJarName(URL jarUrl) { - isUsed = true; - return scanner.getUnqualifiedJarName( jarUrl ); + return delegate.scan( persistenceUnit, options ); } } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java index b1fb148cb1..e4ed5d1ef4 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/JarVisitorTest.java @@ -23,12 +23,6 @@ */ package org.hibernate.jpa.test.packaging; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -38,29 +32,32 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; -import java.util.Set; - -import javax.persistence.Embeddable; -import javax.persistence.Entity; -import javax.persistence.MappedSuperclass; import org.hibernate.dialect.H2Dialect; -import org.hibernate.jpa.packaging.internal.ClassFilter; -import org.hibernate.jpa.packaging.internal.Entry; -import org.hibernate.jpa.packaging.internal.ExplodedJarVisitor; -import org.hibernate.jpa.packaging.internal.FileFilter; -import org.hibernate.jpa.packaging.internal.FileZippedJarVisitor; -import org.hibernate.jpa.packaging.internal.Filter; -import org.hibernate.jpa.packaging.internal.InputStreamZippedJarVisitor; -import org.hibernate.jpa.packaging.internal.JarProtocolVisitor; -import org.hibernate.jpa.packaging.internal.JarVisitor; -import org.hibernate.jpa.packaging.internal.JarVisitorFactory; -import org.hibernate.jpa.packaging.internal.PackageFilter; +import org.hibernate.jpa.boot.archive.internal.ArchiveHelper; +import org.hibernate.jpa.boot.archive.internal.ExplodedArchiveDescriptor; +import org.hibernate.jpa.boot.archive.internal.JarFileBasedArchiveDescriptor; +import org.hibernate.jpa.boot.archive.internal.JarInputStreamBasedArchiveDescriptor; +import org.hibernate.jpa.boot.archive.internal.JarProtocolArchiveDescriptor; +import org.hibernate.jpa.boot.archive.internal.StandardArchiveDescriptorFactory; +import org.hibernate.jpa.boot.archive.spi.ArchiveDescriptor; +import org.hibernate.jpa.boot.internal.ClassDescriptorImpl; +import org.hibernate.jpa.boot.scan.internal.StandardScanOptions; +import org.hibernate.jpa.boot.scan.spi.AbstractScannerImpl; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; +import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter; import org.hibernate.jpa.test.pack.defaultpar.Version; import org.hibernate.jpa.test.pack.explodedpar.Carpet; + +import org.junit.Test; + import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; -import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Emmanuel Bernard @@ -72,7 +69,7 @@ import org.junit.Test; public class JarVisitorTest extends PackagingTestCase { @Test public void testHttp() throws Exception { - URL url = JarVisitorFactory.getJarURLFromURLEntry( + URL url = ArchiveHelper.getJarURLFromURLEntry( new URL( "jar:http://www.ibiblio.org/maven/hibernate/jars/hibernate-annotations-3.0beta1.jar!/META-INF/persistence.xml" ), @@ -86,10 +83,18 @@ public class JarVisitorTest extends PackagingTestCase { //fail silently return; } - JarVisitor visitor = JarVisitorFactory.getVisitor( url, getFilters() ); - assertEquals( 0, visitor.getMatchingEntries()[0].size() ); - assertEquals( 0, visitor.getMatchingEntries()[1].size() ); - assertEquals( 0, visitor.getMatchingEntries()[2].size() ); + ArchiveDescriptor archiveDescriptor = StandardArchiveDescriptorFactory.INSTANCE.buildArchiveDescriptor( url ); + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + assertEquals( 0, resultCollector.getClassDescriptorSet().size() ); + assertEquals( 0, resultCollector.getPackageDescriptorSet().size() ); + assertEquals( 0, resultCollector.getMappingFileSet().size() ); } @Test @@ -97,20 +102,40 @@ public class JarVisitorTest extends PackagingTestCase { File defaultPar = buildDefaultPar(); addPackageToClasspath( defaultPar ); - Filter[] filters = getFilters(); - JarVisitor jarVisitor = new InputStreamZippedJarVisitor( defaultPar.toURL(), filters, "" ); - assertEquals( "defaultpar", jarVisitor.getUnqualifiedJarName() ); - Set entries = jarVisitor.getMatchingEntries()[1]; - assertEquals( 3, entries.size() ); - Entry entry = new Entry( org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - entry = new Entry( Version.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - assertNull( ( ( Entry ) entries.iterator().next() ).getInputStream() ); - assertEquals( 2, jarVisitor.getMatchingEntries()[2].size() ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); + ArchiveDescriptor archiveDescriptor = new JarInputStreamBasedArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + defaultPar.toURL(), + "" + ); + + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + validateResults( resultCollector, org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class, Version.class ); + } + + private void validateResults(AbstractScannerImpl.ResultCollector resultCollector, Class... expectedClasses) throws IOException { + assertEquals( 3, resultCollector.getClassDescriptorSet().size() ); + for ( Class expectedClass : expectedClasses ) { + assertTrue( + resultCollector.getClassDescriptorSet().contains( + new ClassDescriptorImpl( expectedClass.getName(), null ) + ) + ); + } + + assertEquals( 2, resultCollector.getMappingFileSet().size() ); + for ( MappingFileDescriptor mappingFileDescriptor : resultCollector.getMappingFileSet() ) { + assertNotNull( mappingFileDescriptor.getStreamAccess() ); + final InputStream stream = mappingFileDescriptor.getStreamAccess().accessInputStream(); + assertNotNull( stream ); + stream.close(); } } @@ -122,41 +147,39 @@ public class JarVisitorTest extends PackagingTestCase { addPackageToClasspath( nestedEar ); String jarFileName = nestedEar.toURL().toExternalForm() + "!/defaultpar.par"; - Filter[] filters = getFilters(); - JarVisitor jarVisitor = new JarProtocolVisitor( new URL( jarFileName ), filters, "" ); - //TODO should we fix the name here to reach defaultpar rather than nestedjar ?? - //assertEquals( "defaultpar", jarVisitor.getUnqualifiedJarName() ); - Set entries = jarVisitor.getMatchingEntries()[1]; - assertEquals( 3, entries.size() ); - Entry entry = new Entry( org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - entry = new Entry( Version.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - assertNull( ( ( Entry ) entries.iterator().next() ).getInputStream() ); - assertEquals( 2, jarVisitor.getMatchingEntries()[2].size() ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); - } + + JarProtocolArchiveDescriptor archiveDescriptor = new JarProtocolArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + new URL( jarFileName ), + "" + ); + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + validateResults( resultCollector, org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class, Version.class ); jarFileName = nestedEarDir.toURL().toExternalForm() + "!/defaultpar.par"; - //JarVisitor jarVisitor = new ZippedJarVisitor( jarFileName, true, true ); - filters = getFilters(); - jarVisitor = new JarProtocolVisitor( new URL( jarFileName ), filters, "" ); - //TODO should we fix the name here to reach defaultpar rather than nestedjar ?? - //assertEquals( "defaultpar", jarVisitor.getUnqualifiedJarName() ); - entries = jarVisitor.getMatchingEntries()[1]; - assertEquals( 3, entries.size() ); - entry = new Entry( org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - entry = new Entry( Version.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - assertNull( ( ( Entry ) entries.iterator().next() ).getInputStream() ); - assertEquals( 2, jarVisitor.getMatchingEntries()[2].size() ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); - } + archiveDescriptor = new JarProtocolArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + new URL( jarFileName ), + "" + ); + resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + validateResults( resultCollector, org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class, Version.class ); } @Test @@ -165,21 +188,26 @@ public class JarVisitorTest extends PackagingTestCase { addPackageToClasspath( war ); String jarFileName = war.toURL().toExternalForm() + "!/WEB-INF/classes"; - Filter[] filters = getFilters(); - JarVisitor jarVisitor = new JarProtocolVisitor( new URL( jarFileName ), filters, "" ); - assertEquals( "war", jarVisitor.getUnqualifiedJarName() ); - Set entries = jarVisitor.getMatchingEntries()[1]; - assertEquals( 3, entries.size() ); - Entry entry = new Entry( org.hibernate.jpa.test.pack.war.ApplicationServer.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - entry = new Entry( org.hibernate.jpa.test.pack.war.Version.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - assertNull( ( ( Entry ) entries.iterator().next() ).getInputStream() ); - assertEquals( 2, jarVisitor.getMatchingEntries()[2].size() ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); - } + JarProtocolArchiveDescriptor archiveDescriptor = new JarProtocolArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + new URL( jarFileName ), + "" + ); + + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + validateResults( + resultCollector, + org.hibernate.jpa.test.pack.war.ApplicationServer.class, + org.hibernate.jpa.test.pack.war.Version.class + ); } @Test @@ -187,21 +215,21 @@ public class JarVisitorTest extends PackagingTestCase { File defaultPar = buildDefaultPar(); addPackageToClasspath( defaultPar ); - Filter[] filters = getFilters(); - JarVisitor jarVisitor = new FileZippedJarVisitor( defaultPar.toURL(), filters, "" ); - assertEquals( "defaultpar", jarVisitor.getUnqualifiedJarName() ); - Set entries = jarVisitor.getMatchingEntries()[1]; - assertEquals( 3, entries.size() ); - Entry entry = new Entry( org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - entry = new Entry( Version.class.getName(), null ); - assertTrue( entries.contains( entry ) ); - assertNull( ( ( Entry ) entries.iterator().next() ).getInputStream() ); - assertEquals( 2, jarVisitor.getMatchingEntries()[2].size() ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); - } + JarFileBasedArchiveDescriptor archiveDescriptor = new JarFileBasedArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + defaultPar.toURL(), + "" + ); + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + validateResults( resultCollector, org.hibernate.jpa.test.pack.defaultpar.ApplicationServer.class, Version.class ); } @Test @@ -209,32 +237,50 @@ public class JarVisitorTest extends PackagingTestCase { File explodedPar = buildExplodedPar(); addPackageToClasspath( explodedPar ); - Filter[] filters = getFilters(); String dirPath = explodedPar.toURL().toExternalForm(); // TODO - shouldn't ExplodedJarVisitor take care of a trailing slash? if ( dirPath.endsWith( "/" ) ) { dirPath = dirPath.substring( 0, dirPath.length() - 1 ); } - JarVisitor jarVisitor = new ExplodedJarVisitor( dirPath, filters ); - assertEquals( "explodedpar", jarVisitor.getUnqualifiedJarName() ); - Set[] entries = jarVisitor.getMatchingEntries(); - assertEquals( 1, entries[1].size() ); - assertEquals( 1, entries[0].size() ); - assertEquals( 1, entries[2].size() ); - Entry entry = new Entry( Carpet.class.getName(), null ); - assertTrue( entries[1].contains( entry ) ); - for ( Entry localEntry : ( Set ) jarVisitor.getMatchingEntries()[2] ) { - assertNotNull( localEntry.getInputStream() ); - localEntry.getInputStream().close(); + ExplodedArchiveDescriptor archiveDescriptor = new ExplodedArchiveDescriptor( + StandardArchiveDescriptorFactory.INSTANCE, + ArchiveHelper.getURLFromPath( dirPath ), + "" + ); + AbstractScannerImpl.ResultCollector resultCollector = new AbstractScannerImpl.ResultCollector( new StandardScanOptions() ); + archiveDescriptor.visitArchive( + new AbstractScannerImpl.ArchiveContextImpl( + new PersistenceUnitDescriptorAdapter(), + true, + resultCollector + ) + ); + + assertEquals( 1, resultCollector.getClassDescriptorSet().size() ); + assertEquals( 1, resultCollector.getPackageDescriptorSet().size() ); + assertEquals( 1, resultCollector.getMappingFileSet().size() ); + + assertTrue( + resultCollector.getClassDescriptorSet().contains( + new ClassDescriptorImpl( Carpet.class.getName(), null ) + ) + ); + + for ( MappingFileDescriptor mappingFileDescriptor : resultCollector.getMappingFileSet() ) { + assertNotNull( mappingFileDescriptor.getStreamAccess() ); + final InputStream stream = mappingFileDescriptor.getStreamAccess().accessInputStream(); + assertNotNull( stream ); + stream.close(); } } @Test @TestForIssue(jiraKey = "HHH-6806") - public void testJarVisitorFactory() throws Exception{ - - addPackageToClasspath( buildExplodedPar(), buildDefaultPar() ); + public void testJarVisitorFactory() throws Exception { + final File explodedPar = buildExplodedPar(); + final File defaultPar = buildDefaultPar(); + addPackageToClasspath( explodedPar, defaultPar ); //setting URL to accept vfs based protocol URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { @@ -250,21 +296,21 @@ public class JarVisitorTest extends PackagingTestCase { } }); - URL jarUrl = new URL ("file:./target/packages/defaultpar.par"); - JarVisitor jarVisitor = JarVisitorFactory.getVisitor(jarUrl, getFilters(), null); - assertEquals(FileZippedJarVisitor.class.getName(), jarVisitor.getClass().getName()); - - jarUrl = new URL ("file:./target/packages/explodedpar"); - jarVisitor = JarVisitorFactory.getVisitor(jarUrl, getFilters(), null); - assertEquals(ExplodedJarVisitor.class.getName(), jarVisitor.getClass().getName()); - - jarUrl = new URL ("vfszip:./target/packages/defaultpar.par"); - jarVisitor = JarVisitorFactory.getVisitor(jarUrl, getFilters(), null); - assertEquals(FileZippedJarVisitor.class.getName(), jarVisitor.getClass().getName()); - - jarUrl = new URL ("vfsfile:./target/packages/explodedpar"); - jarVisitor = JarVisitorFactory.getVisitor(jarUrl, getFilters(), null); - assertEquals(ExplodedJarVisitor.class.getName(), jarVisitor.getClass().getName()); + URL jarUrl = defaultPar.toURL(); + ArchiveDescriptor descriptor = StandardArchiveDescriptorFactory.INSTANCE.buildArchiveDescriptor( jarUrl ); + assertEquals( JarFileBasedArchiveDescriptor.class.getName(), descriptor.getClass().getName() ); + + jarUrl = explodedPar.toURL(); + descriptor = StandardArchiveDescriptorFactory.INSTANCE.buildArchiveDescriptor( jarUrl ); + assertEquals( ExplodedArchiveDescriptor.class.getName(), descriptor.getClass().getName() ); + + jarUrl = new URL( defaultPar.toURL().toExternalForm().replace( "file:", "vfszip:" ) ); + descriptor = StandardArchiveDescriptorFactory.INSTANCE.buildArchiveDescriptor( jarUrl ); + assertEquals( JarFileBasedArchiveDescriptor.class.getName(), descriptor.getClass().getName()); + + jarUrl = new URL( explodedPar.toURL().toExternalForm().replace( "file:", "vfsfile:" ) ); + descriptor = StandardArchiveDescriptorFactory.INSTANCE.buildArchiveDescriptor( jarUrl ); + assertEquals( ExplodedArchiveDescriptor.class.getName(), descriptor.getClass().getName() ); } @Test @@ -315,36 +361,29 @@ public class JarVisitorTest extends PackagingTestCase { @Test @TestForIssue(jiraKey = "HHH-7835") - public void testGetBytesFromInputStream() { - try { - File file = buildLargeJar(); + public void testGetBytesFromInputStream() throws Exception { + File file = buildLargeJar(); - long start = System.currentTimeMillis(); - InputStream stream = new BufferedInputStream( - new FileInputStream( file ) ); - int oldLength = getBytesFromInputStream( stream ).length; - stream.close(); - long oldTime = System.currentTimeMillis() - start; + long start = System.currentTimeMillis(); + InputStream stream = new BufferedInputStream( + new FileInputStream( file ) ); + int oldLength = getBytesFromInputStream( stream ).length; + stream.close(); + long oldTime = System.currentTimeMillis() - start; - start = System.currentTimeMillis(); - stream = new BufferedInputStream( new FileInputStream( file ) ); - int newLength = JarVisitorFactory.getBytesFromInputStream( - stream ).length; - stream.close(); - long newTime = System.currentTimeMillis() - start; + start = System.currentTimeMillis(); + stream = new BufferedInputStream( new FileInputStream( file ) ); + int newLength = ArchiveHelper.getBytesFromInputStream( stream ).length; + stream.close(); + long newTime = System.currentTimeMillis() - start; - assertEquals( oldLength, newLength ); - assertTrue( oldTime > newTime ); - } - catch ( Exception e ) { - fail( e.getMessage() ); - } + assertEquals( oldLength, newLength ); + assertTrue( oldTime > newTime ); } // This is the old getBytesFromInputStream from JarVisitorFactory before // it was changed by HHH-7835. Use it as a regression test. - private byte[] getBytesFromInputStream( - InputStream inputStream) throws IOException { + private byte[] getBytesFromInputStream(InputStream inputStream) throws IOException { int size; byte[] entryBytes = new byte[0]; @@ -363,46 +402,16 @@ public class JarVisitorTest extends PackagingTestCase { @Test @TestForIssue(jiraKey = "HHH-7835") - public void testGetBytesFromZeroInputStream() { - try { - // Ensure that JarVisitorFactory#getBytesFromInputStream - // can handle 0 length streams gracefully. - InputStream emptyStream = new BufferedInputStream( - new FileInputStream( new File( - "src/test/resources/org/hibernate/jpa/test/packaging/empty.txt" ) ) ); - int length = JarVisitorFactory.getBytesFromInputStream( - emptyStream ).length; - assertEquals( length, 0 ); - emptyStream.close(); + public void testGetBytesFromZeroInputStream() throws Exception { + // Ensure that JarVisitorFactory#getBytesFromInputStream + // can handle 0 length streams gracefully. + URL emptyTxtUrl = getClass().getResource( "/org/hibernate/jpa/test/packaging/empty.txt" ); + if ( emptyTxtUrl == null ) { + throw new RuntimeException( "Bah!" ); } - catch ( Exception e ) { - fail( e.getMessage() ); - } - } - - private Filter[] getFilters() { - return new Filter[] { - new PackageFilter( false, null ) { - public boolean accept(String javaElementName) { - return true; - } - }, - new ClassFilter( - false, new Class[] { - Entity.class, - MappedSuperclass.class, - Embeddable.class - } - ) { - public boolean accept(String javaElementName) { - return true; - } - }, - new FileFilter( true ) { - public boolean accept(String javaElementName) { - return javaElementName.endsWith( "hbm.xml" ) || javaElementName.endsWith( "META-INF/orm.xml" ); - } - } - }; + InputStream emptyStream = new BufferedInputStream( emptyTxtUrl.openStream() ); + int length = ArchiveHelper.getBytesFromInputStream( emptyStream ).length; + assertEquals( length, 0 ); + emptyStream.close(); } } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java index 30582808f6..af86c8aeae 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java @@ -45,6 +45,7 @@ import org.hibernate.jpa.test.Cat; import org.hibernate.jpa.test.Distributor; import org.hibernate.jpa.test.Item; import org.hibernate.jpa.test.Kitten; +import org.hibernate.jpa.test.TestHelper; import org.hibernate.jpa.test.pack.cfgxmlpar.Morito; import org.hibernate.jpa.test.pack.defaultpar.ApplicationServer; import org.hibernate.jpa.test.pack.defaultpar.IncrementListener; @@ -204,6 +205,10 @@ public abstract class PackagingTestCase extends BaseCoreFunctionalTestCase { } protected File buildExplicitPar() { + // explicitpar/persistence.xml references externaljar.jar so build that from here. + // this is the reason for tests failing after clean at least on my (Steve) local system + buildExternalJar(); + String fileName = "explicitpar.par"; JavaArchive archive = ShrinkWrap.create( JavaArchive.class, fileName ); archive.addClasses( @@ -342,8 +347,10 @@ public abstract class PackagingTestCase extends BaseCoreFunctionalTestCase { // Build a large jar by adding a lorem ipsum file repeatedly. for ( int i = 0; i < 100; i++ ) { ArchivePath path = ArchivePaths.create( "META-INF/file" + i ); - archive.addAsResource( new File( "src/test/resources/org/hibernate/jpa/test/packaging/loremipsum.txt" ), - path ); + archive.addAsResource( + "org/hibernate/jpa/test/packaging/loremipsum.txt", + path + ); } File testPackage = new File( packageTargetDir, fileName ); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/ScannerTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/ScannerTest.java index ed2bc44952..28ed10ab8e 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/ScannerTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/ScannerTest.java @@ -23,31 +23,32 @@ */ package org.hibernate.jpa.test.packaging; -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import javax.persistence.Converter; -import javax.persistence.Embeddable; -import javax.persistence.Entity; import javax.persistence.EntityManagerFactory; -import javax.persistence.MappedSuperclass; import javax.persistence.Persistence; - -import org.junit.Test; +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; import org.hibernate.jpa.AvailableSettings; -import org.hibernate.jpa.packaging.internal.NativeScanner; - +import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.boot.scan.internal.StandardScanOptions; +import org.hibernate.jpa.boot.scan.internal.StandardScanner; +import org.hibernate.jpa.boot.spi.ClassDescriptor; +import org.hibernate.jpa.boot.spi.MappingFileDescriptor; +import org.hibernate.jpa.boot.spi.NamedInputStream; +import org.hibernate.jpa.boot.scan.spi.ScanOptions; +import org.hibernate.jpa.boot.scan.spi.ScanResult; +import org.hibernate.jpa.boot.scan.spi.Scanner; import org.hibernate.jpa.test.pack.defaultpar.ApplicationServer; -import org.hibernate.jpa.packaging.spi.NamedInputStream; -import org.hibernate.jpa.packaging.spi.Scanner; import org.hibernate.jpa.test.pack.defaultpar.Version; +import org.junit.Test; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Emmanuel Bernard @@ -59,32 +60,39 @@ public class ScannerTest extends PackagingTestCase { File defaultPar = buildDefaultPar(); addPackageToClasspath( defaultPar ); - Scanner scanner = new NativeScanner(); - assertEquals( "defaultpar", scanner.getUnqualifiedJarName( defaultPar.toURL() ) ); + PersistenceUnitDescriptor descriptor = new ParsedPersistenceXmlDescriptor( defaultPar.toURL() ); + ScanOptions options = new StandardScanOptions( "hbm,class", descriptor.isExcludeUnlistedClasses() ); + Scanner scanner = new StandardScanner(); + ScanResult scanResult = scanner.scan( descriptor, options ); - Set> annotationsToLookFor = new HashSet>( 3 ); - annotationsToLookFor.add( Entity.class ); - annotationsToLookFor.add( MappedSuperclass.class ); - annotationsToLookFor.add( Embeddable.class ); - annotationsToLookFor.add( Converter.class ); - final Set> classes = scanner.getClassesInJar( defaultPar.toURL(), annotationsToLookFor ); + assertEquals( 3, scanResult.getLocatedClasses().size() ); + assertClassesContained( scanResult, ApplicationServer.class ); + assertClassesContained( scanResult, Version.class ); - assertEquals( 3, classes.size() ); - assertTrue( classes.contains( ApplicationServer.class ) ); - assertTrue( classes.contains( Version.class ) ); - - Set filePatterns = new HashSet( 2 ); - filePatterns.add( "**/*.hbm.xml" ); - filePatterns.add( "META-INF/orm.xml" ); - final Set files = scanner.getFilesInJar( defaultPar.toURL(), filePatterns ); - - assertEquals( 2, files.size() ); - for ( NamedInputStream file : files ) { - assertNotNull( file.getStream() ); - file.getStream().close(); + assertEquals( 2, scanResult.getLocatedMappingFiles().size() ); + for ( MappingFileDescriptor mappingFileDescriptor : scanResult.getLocatedMappingFiles() ) { + assertNotNull( mappingFileDescriptor.getName() ); + assertNotNull( mappingFileDescriptor.getStreamAccess() ); + InputStream stream = mappingFileDescriptor.getStreamAccess().accessInputStream(); + assertNotNull( stream ); + stream.close(); + NamedInputStream namedInputStream = mappingFileDescriptor.getStreamAccess().asNamedInputStream(); + assertNotNull( namedInputStream ); + stream = namedInputStream.getStream(); + assertNotNull( stream ); + stream.close(); } } + private void assertClassesContained(ScanResult scanResult, Class classToCheckFor) { + for ( ClassDescriptor classDescriptor : scanResult.getLocatedClasses() ) { + if ( classDescriptor.getName().equals( classToCheckFor.getName() ) ) { + return; + } + } + fail( "ScanResult did not contain expected Class : " + classToCheckFor.getName() ); + } + @Test public void testCustomScanner() throws Exception { File defaultPar = buildDefaultPar(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java index b6ee70c6de..2cbd8a0553 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java @@ -50,6 +50,7 @@ import org.hibernate.persister.collection.AbstractCollectionPersister; * @author HernпїЅn Chanfreau * @author Steve Ebersole * @author Michal Skowronek (mskowr at o2 dot pl) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventListener { protected BaseEnversCollectionEventListener(AuditConfiguration enversConfiguration) { @@ -65,15 +66,12 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL PersistentCollection newColl, Serializable oldColl, CollectionEntry collectionEntry) { - String entityName = event.getAffectedOwnerEntityName(); - if ( ! getAuditConfiguration().getGlobalCfg().isGenerateRevisionsForCollections() ) { - return; - } - if ( getAuditConfiguration().getEntCfg().isVersioned( entityName ) ) { + if ( shouldGenerateRevision( event ) ) { checkIfTransactionInProgress(event.getSession()); AuditProcess auditProcess = getAuditConfiguration().getSyncManager().get(event.getSession()); + String entityName = event.getAffectedOwnerEntityName(); String ownerEntityName = ((AbstractCollectionPersister) collectionEntry.getLoadedPersister()).getOwnerEntityName(); String referencingPropertyName = collectionEntry.getRole().substring(ownerEntityName.length() + 1); @@ -123,6 +121,27 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL } } + /** + * Forces persistent collection initialization. + * @param event Collection event. + * @return Stored snapshot. + */ + protected Serializable initializeCollection(AbstractCollectionEvent event) { + event.getCollection().forceInitialization(); + return event.getCollection().getStoredSnapshot(); + } + + /** + * Checks whether modification of not-owned relation field triggers new revision and owner entity is versioned. + * @param event Collection event. + * @return {@code true} if revision based on given event should be generated, {@code false} otherwise. + */ + protected boolean shouldGenerateRevision(AbstractCollectionEvent event) { + final String entityName = event.getAffectedOwnerEntityName(); + return getAuditConfiguration().getGlobalCfg().isGenerateRevisionsForCollections() + && getAuditConfiguration().getEntCfg().isVersioned( entityName ); + } + /** * Looks up a relation description corresponding to the given property in the given entity. If no description is * found in the given entity, the parent entity is checked (so that inherited relations work). diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPreCollectionRemoveEventListenerImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPreCollectionRemoveEventListenerImpl.java index ab90d7ca26..84ed830874 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPreCollectionRemoveEventListenerImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPreCollectionRemoveEventListenerImpl.java @@ -23,6 +23,8 @@ */ package org.hibernate.envers.event; +import java.io.Serializable; + import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.event.spi.PreCollectionRemoveEvent; @@ -32,6 +34,7 @@ import org.hibernate.event.spi.PreCollectionRemoveEventListener; * @author Adam Warski (adam at warski dot org) * @author HernпїЅn Chanfreau * @author Steve Ebersole + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class EnversPreCollectionRemoveEventListenerImpl extends BaseEnversCollectionEventListener @@ -45,7 +48,12 @@ public class EnversPreCollectionRemoveEventListenerImpl public void onPreRemoveCollection(PreCollectionRemoveEvent event) { CollectionEntry collectionEntry = getCollectionEntry( event ); if ( collectionEntry != null && !collectionEntry.getLoadedPersister().isInverse() ) { - onCollectionAction( event, null, collectionEntry.getSnapshot(), collectionEntry ); + Serializable oldColl = collectionEntry.getSnapshot(); + if ( !event.getCollection().wasInitialized() && shouldGenerateRevision( event ) ) { + // In case of uninitialized collection we need a fresh snapshot to properly calculate audit data. + oldColl = initializeCollection( event ); + } + onCollectionAction( event, null, oldColl, collectionEntry ); } } } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/JoinTableEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/JoinTableEntity.java new file mode 100644 index 0000000000..1e5c162b08 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/JoinTableEntity.java @@ -0,0 +1,95 @@ +package org.hibernate.envers.test.entities.manytomany.unidirectional; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrTestEntity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class JoinTableEntity implements Serializable { + @Id + @GeneratedValue + private Long id; + + private String data; + + @ManyToMany + @JoinTable(name = "test_join_table", + joinColumns = @JoinColumn(name = "assoc_id1"), + inverseJoinColumns = @JoinColumn(name = "assoc_id2") + ) + private Set references = new HashSet(); + + public JoinTableEntity() { + } + + public JoinTableEntity(String data) { + this.data = data; + } + + public JoinTableEntity(Long id, String data) { + this.id = id; + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof JoinTableEntity ) ) return false; + + JoinTableEntity that = (JoinTableEntity) o; + + if ( data != null ? !data.equals( that.data ) : that.data != null ) return false; + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( data != null ? data.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "JoinTableEntity(id = " + id + ", data = " + data + ")"; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getReferences() { + return references; + } + + public void setReferences(Set references) { + this.references = references; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/JoinTableDetachedTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/JoinTableDetachedTest.java new file mode 100644 index 0000000000..78b6a86d72 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/JoinTableDetachedTest.java @@ -0,0 +1,137 @@ +package org.hibernate.envers.test.integration.manytomany.unidirectional; + +import java.util.Arrays; +import java.util.HashSet; +import javax.persistence.EntityManager; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.StrTestEntity; +import org.hibernate.envers.test.entities.manytomany.unidirectional.JoinTableEntity; +import org.hibernate.testing.TestForIssue; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue( jiraKey = "HHH-8087" ) +public class JoinTableDetachedTest extends BaseEnversJPAFunctionalTestCase { + private Long collectionEntityId = null; + private Integer element1Id = null; + private Integer element2Id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { JoinTableEntity.class, StrTestEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - addition + em.getTransaction().begin(); + JoinTableEntity collectionEntity = new JoinTableEntity( "some data" ); + StrTestEntity element1 = new StrTestEntity( "str1" ); + StrTestEntity element2 = new StrTestEntity( "str2" ); + collectionEntity.getReferences().add( element1 ); + collectionEntity.getReferences().add( element2 ); + em.persist( element1 ); + em.persist( element2 ); + em.persist( collectionEntity ); + em.getTransaction().commit(); + + collectionEntityId = collectionEntity.getId(); + element1Id = element1.getId(); + element2Id = element2.getId(); + + em.close(); + em = getEntityManager(); + + // Revision 2 - simple modification + em.getTransaction().begin(); + collectionEntity = em.find( JoinTableEntity.class, collectionEntity.getId() ); + collectionEntity.setData( "some other data" ); + collectionEntity = em.merge( collectionEntity ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // Revision 3 - remove detached object from collection + em.getTransaction().begin(); + collectionEntity = em.find( JoinTableEntity.class, collectionEntity.getId() ); + collectionEntity.getReferences().remove( element1 ); + collectionEntity = em.merge( collectionEntity ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // Revision 4 - replace the collection + em.getTransaction().begin(); + collectionEntity = em.find( JoinTableEntity.class, collectionEntity.getId() ); + collectionEntity.setReferences( new HashSet() ); + collectionEntity = em.merge( collectionEntity ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // Revision 5 - add to collection + em.getTransaction().begin(); + collectionEntity = em.find( JoinTableEntity.class, collectionEntity.getId() ); + collectionEntity.getReferences().add( element1 ); + collectionEntity = em.merge( collectionEntity ); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + Assert.assertEquals( Arrays.asList( 1, 2, 3, 4, 5 ), getAuditReader().getRevisions(JoinTableEntity.class, collectionEntityId ) ); + Assert.assertEquals( Arrays.asList( 1 ), getAuditReader().getRevisions(StrTestEntity.class, element1Id ) ); + Assert.assertEquals( Arrays.asList( 1 ), getAuditReader().getRevisions(StrTestEntity.class, element2Id ) ); + } + + @Test + public void testHistoryOfCollectionEntity() { + // Revision 1 + JoinTableEntity collectionEntity = new JoinTableEntity( collectionEntityId, "some data" ); + StrTestEntity element1 = new StrTestEntity( "str1", element1Id ); + StrTestEntity element2 = new StrTestEntity( "str2", element2Id ); + collectionEntity.getReferences().add( element1 ); + collectionEntity.getReferences().add( element2 ); + JoinTableEntity ver1 = getAuditReader().find( JoinTableEntity.class, collectionEntityId, 1 ); + Assert.assertEquals( collectionEntity, ver1 ); + Assert.assertEquals( collectionEntity.getReferences(), ver1.getReferences() ); + + // Revision 2 + collectionEntity.setData( "some other data" ); + JoinTableEntity ver2 = getAuditReader().find( JoinTableEntity.class, collectionEntityId, 2 ); + Assert.assertEquals( collectionEntity, ver2 ); + Assert.assertEquals( collectionEntity.getReferences(), ver2.getReferences() ); + + // Revision 3 + collectionEntity.getReferences().remove( element1 ); + JoinTableEntity ver3 = getAuditReader().find( JoinTableEntity.class, collectionEntityId, 3 ); + Assert.assertEquals( collectionEntity, ver3 ); + Assert.assertEquals( collectionEntity.getReferences(), ver3.getReferences() ); + + // Revision 4 + collectionEntity.setReferences( new HashSet() ); + JoinTableEntity ver4 = getAuditReader().find( JoinTableEntity.class, collectionEntityId, 4 ); + Assert.assertEquals( collectionEntity, ver4 ); + Assert.assertEquals( collectionEntity.getReferences(), ver4.getReferences() ); + + // Revision 5 + collectionEntity.getReferences().add( element1 ); + JoinTableEntity ver5 = getAuditReader().find( JoinTableEntity.class, collectionEntityId, 5 ); + Assert.assertEquals( collectionEntity, ver5 ); + Assert.assertEquals( collectionEntity.getReferences(), ver5.getReferences() ); + } +} diff --git a/libraries.gradle b/libraries.gradle index c34ee46d6e..36aa4892b5 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -58,7 +58,7 @@ ext { // javax jpa: 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Draft-14', - jta: 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.0.Final', + jta: 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Alpha1', validation: 'javax.validation:validation-api:1.0.0.GA', jacc: 'org.jboss.spec.javax.security.jacc:jboss-jacc-api_1.4_spec:1.0.2.Final',