org.hibernate.boot.MetadataSources.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.boot.MetadataSources.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.boot;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.xml.transform.dom.DOMSource;

import org.hibernate.HibernateException;
import org.hibernate.boot.archive.spi.InputStreamAccess;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.boot.jaxb.internal.CacheableFileXmlSource;
import org.hibernate.boot.jaxb.internal.JarFileEntryXmlSource;
import org.hibernate.boot.jaxb.internal.JaxpSourceXmlSource;
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.MetadataBuilderFactory;
import org.hibernate.boot.spi.XmlMappingBinderAccess;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.SerializationException;
import org.w3c.dom.Document;

/**
 * Entry point into working with sources of metadata information (mapping XML, annotations).   Tell Hibernate
 * about sources and then call {@link #buildMetadata()}, or use {@link #getMetadataBuilder()} to customize
 * how sources are processed (naming strategies, etc).
 *
 * @author Steve Ebersole
 *
 * @since 5.0
 */
public class MetadataSources implements Serializable {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(MetadataSources.class);

    private final ServiceRegistry serviceRegistry;

    private XmlMappingBinderAccess xmlMappingBinderAccess;

    private List<Binding> xmlBindings;
    private LinkedHashSet<Class<?>> annotatedClasses;
    private LinkedHashSet<String> annotatedClassNames;
    private LinkedHashSet<String> annotatedPackages;

    public MetadataSources() {
        this(new BootstrapServiceRegistryBuilder().build());
    }

    /**
     * Create a metadata sources using the specified service registry.
     *
     * @param serviceRegistry The service registry to use.
     */
    public MetadataSources(ServiceRegistry serviceRegistry) {
        // service registry really should be either BootstrapServiceRegistry or StandardServiceRegistry type...
        if (!isExpectedServiceRegistryType(serviceRegistry)) {
            if (LOG.isDebugEnabled()) {
                LOG.debugf(
                        "Unexpected ServiceRegistry type [%s] encountered during building of MetadataSources; may cause "
                                + "problems later attempting to construct MetadataBuilder",
                        serviceRegistry.getClass().getName());
            }
        }
        this.serviceRegistry = serviceRegistry;
    }

    protected static boolean isExpectedServiceRegistryType(ServiceRegistry serviceRegistry) {
        return BootstrapServiceRegistry.class.isInstance(serviceRegistry)
                || StandardServiceRegistry.class.isInstance(serviceRegistry);
    }

    public XmlMappingBinderAccess getXmlMappingBinderAccess() {
        if (xmlMappingBinderAccess == null) {
            xmlMappingBinderAccess = new XmlMappingBinderAccess(serviceRegistry);
        }
        return xmlMappingBinderAccess;
    }

    public List<Binding> getXmlBindings() {
        return xmlBindings == null ? Collections.emptyList() : xmlBindings;
    }

    public Collection<String> getAnnotatedPackages() {
        return annotatedPackages == null ? Collections.emptySet() : annotatedPackages;
    }

    public Collection<Class<?>> getAnnotatedClasses() {
        return annotatedClasses == null ? Collections.emptySet() : annotatedClasses;
    }

    public Collection<String> getAnnotatedClassNames() {
        return annotatedClassNames == null ? Collections.emptySet() : annotatedClassNames;
    }

    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    /**
     * Get a builder for metadata where non-default options can be specified.
     *
     * @return The built metadata.
     */
    public MetadataBuilder getMetadataBuilder() {
        MetadataBuilderImpl defaultBuilder = new MetadataBuilderImpl(this);
        return getCustomBuilderOrDefault(defaultBuilder);
    }

    /**
     * Get a builder for metadata where non-default options can be specified.
     *
     * @return The built metadata.
     * @deprecated Use {@link #getMetadataBuilder()} instead
     */
    @Deprecated
    public MetadataBuilder getMetadataBuilder(StandardServiceRegistry serviceRegistry) {
        MetadataBuilderImpl defaultBuilder = new MetadataBuilderImpl(this, serviceRegistry);
        return getCustomBuilderOrDefault(defaultBuilder);
    }

    /**
     * In case a custom {@link MetadataBuilderFactory} creates a custom builder, return that one, otherwise the default
     * builder.
     */
    private MetadataBuilder getCustomBuilderOrDefault(MetadataBuilderImpl defaultBuilder) {
        final ClassLoaderService cls = serviceRegistry.getService(ClassLoaderService.class);
        final java.util.Collection<MetadataBuilderFactory> discoveredBuilderFactories = cls
                .loadJavaServices(MetadataBuilderFactory.class);

        MetadataBuilder builder = null;
        List<String> activeFactoryNames = null;

        for (MetadataBuilderFactory discoveredBuilderFactory : discoveredBuilderFactories) {
            final MetadataBuilder returnedBuilder = discoveredBuilderFactory.getMetadataBuilder(this,
                    defaultBuilder);
            if (returnedBuilder != null) {
                if (activeFactoryNames == null) {
                    activeFactoryNames = new ArrayList<>();
                }
                activeFactoryNames.add(discoveredBuilderFactory.getClass().getName());
                builder = returnedBuilder;
            }
        }

        if (activeFactoryNames != null && activeFactoryNames.size() > 1) {
            throw new HibernateException("Multiple active MetadataBuilder definitions were discovered : "
                    + String.join(", ", activeFactoryNames));
        }

        return builder != null ? builder : defaultBuilder;
    }

    /**
     * Short-hand form of calling {@link #getMetadataBuilder()} and using its
     * {@link org.hibernate.boot.MetadataBuilder#build()} method in cases where the application wants
     * to accept the defaults.
     *
     * @return The built metadata.
     */
    public Metadata buildMetadata() {
        return getMetadataBuilder().build();
    }

    public Metadata buildMetadata(StandardServiceRegistry serviceRegistry) {
        return getMetadataBuilder(serviceRegistry).build();
    }

    /**
     * Read metadata from the annotations attached to the given class.
     *
     * @param annotatedClass The class containing annotations
     *
     * @return this (for method chaining)
     */
    public MetadataSources addAnnotatedClass(Class annotatedClass) {
        if (annotatedClasses == null) {
            annotatedClasses = new LinkedHashSet<>();
        }
        annotatedClasses.add(annotatedClass);
        return this;
    }

    /**
     * Read metadata from the annotations attached to the given class.  The important
     * distinction here is that the {@link Class} will not be accessed until later
     * which is important for on-the-fly bytecode-enhancement
     *
     * @param annotatedClassName The name of a class containing annotations
     *
     * @return this (for method chaining)
     */
    public MetadataSources addAnnotatedClassName(String annotatedClassName) {
        if (annotatedClassNames == null) {
            annotatedClassNames = new LinkedHashSet<>();
        }
        annotatedClassNames.add(annotatedClassName);
        return this;
    }

    /**
     * Read package-level metadata.
     *
     * @param packageName java package name without trailing '.', cannot be {@code null}
     *
     * @return this (for method chaining)
     */
    public MetadataSources addPackage(String packageName) {
        if (packageName == null) {
            throw new IllegalArgumentException("The specified package name cannot be null");
        }

        if (packageName.endsWith(".")) {
            packageName = packageName.substring(0, packageName.length() - 1);
        }

        addPackageInternal(packageName);
        return this;
    }

    private void addPackageInternal(String packageName) {
        if (annotatedPackages == null) {
            annotatedPackages = new LinkedHashSet<>();
        }
        annotatedPackages.add(packageName);
    }

    /**
     * Read package-level metadata.
     *
     * @param packageRef Java Package reference
     *
     * @return this (for method chaining)
     */
    public MetadataSources addPackage(Package packageRef) {
        addPackageInternal(packageRef.getName());
        return this;
    }

    /**
     * Read a mapping as an application resource using the convention that a class named {@code foo.bar.Foo} is
     * mapped by a file named {@code foo/bar/Foo.hbm.xml} which can be resolved as a classpath resource.
     *
     * @param entityClass The mapped class. Cannot be {@code null} null.
     *
     * @return this (for method chaining purposes)
     *
     * @deprecated hbm.xml is a legacy mapping format now considered deprecated.
     */
    @Deprecated
    public MetadataSources addClass(Class entityClass) {
        if (entityClass == null) {
            throw new IllegalArgumentException("The specified class cannot be null");
        }
        if (LOG.isDebugEnabled()) {
            LOG.debugf("adding resource mappings from class convention : %s", entityClass.getName());
        }
        final String mappingResourceName = entityClass.getName().replace('.', '/') + ".hbm.xml";
        addResource(mappingResourceName);
        return this;
    }

    /**
     * Read mappings as a application resourceName (i.e. classpath lookup).
     *
     * @param name The resource name
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addResource(String name) {
        getXmlBindingsForWrite().add(getXmlMappingBinderAccess().bind(name));
        return this;
    }

    /**
     * Read mappings from a particular XML file
     *
     * @param path The path to a file.  Expected to be resolvable by {@link java.io.File#File(String)}
     *
     * @return this (for method chaining purposes)
     *
     * @see #addFile(java.io.File)
     */
    public MetadataSources addFile(String path) {
        addFile(new File(path));
        return this;
    }

    /**
     * Read mappings from a particular XML file
     *
     * @param file The reference to the XML file
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addFile(File file) {
        getXmlBindingsForWrite().add(getXmlMappingBinderAccess().bind(file));
        return this;
    }

    /**
     * See {@link #addCacheableFile(java.io.File)} for description
     *
     * @param path The path to a file.  Expected to be resolvable by {@link java.io.File#File(String)}
     *
     * @return this (for method chaining purposes)
     *
     * @see #addCacheableFile(java.io.File)
     */
    public MetadataSources addCacheableFile(String path) {
        final Origin origin = new Origin(SourceType.FILE, path);
        addCacheableFile(origin, new File(path));
        return this;
    }

    private void addCacheableFile(Origin origin, File file) {
        getXmlBindingsForWrite().add(new CacheableFileXmlSource(origin, file, false)
                .doBind(getXmlMappingBinderAccess().getMappingBinder()));
    }

    /**
     * Add a cached mapping file.  A cached file is a serialized representation of the DOM structure of a
     * particular mapping.  It is saved from a previous call as a file with the name {@code {xmlFile}.bin}
     * where {@code {xmlFile}} is the name of the original mapping file.
     * </p>
     * If a cached {@code {xmlFile}.bin} exists and is newer than {@code {xmlFile}}, the {@code {xmlFile}.bin}
     * file will be read directly. Otherwise {@code {xmlFile}} is read and then serialized to {@code {xmlFile}.bin} for
     * use the next time.
     *
     * @param file The cacheable mapping file to be added, {@code {xmlFile}} in above discussion.
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addCacheableFile(File file) {
        final Origin origin = new Origin(SourceType.FILE, file.getName());
        addCacheableFile(origin, file);
        return this;
    }

    /**
     * <b>INTENDED FOR TESTSUITE USE ONLY!</b>
     * <p/>
     * Much like {@link #addCacheableFile(java.io.File)} except that here we will fail immediately if
     * the cache version cannot be found or used for whatever reason
     *
     * @param file The xml file, not the bin!
     *
     * @return The dom "deserialized" from the cached file.
     *
     * @throws org.hibernate.type.SerializationException Indicates a problem deserializing the cached dom tree
     * @throws java.io.FileNotFoundException Indicates that the cached file was not found or was not usable.
     */
    public MetadataSources addCacheableFileStrictly(File file)
            throws SerializationException, FileNotFoundException {
        final Origin origin = new Origin(SourceType.FILE, file.getAbsolutePath());
        getXmlBindingsForWrite().add(new CacheableFileXmlSource(origin, file, true)
                .doBind(getXmlMappingBinderAccess().getMappingBinder()));
        return this;
    }

    /**
     * Read metadata from an {@link java.io.InputStream} access
     *
     * @param xmlInputStreamAccess Access to an input stream containing a DOM.
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addInputStream(InputStreamAccess xmlInputStreamAccess) {
        getXmlBindingsForWrite().add(getXmlMappingBinderAccess().bind(xmlInputStreamAccess));
        return this;
    }

    /**
     * Read metadata from an {@link java.io.InputStream}.
     *
     * @param xmlInputStream The input stream containing a DOM.
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addInputStream(InputStream xmlInputStream) {
        getXmlBindingsForWrite().add(getXmlMappingBinderAccess().bind(xmlInputStream));
        return this;
    }

    /**
     * Read mappings from a {@link java.net.URL}
     *
     * @param url The url for the mapping document to be read.
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addURL(URL url) {
        getXmlBindingsForWrite().add(getXmlMappingBinderAccess().bind(url));
        return this;
    }

    /**
     * Read mappings from a DOM {@link org.w3c.dom.Document}
     *
     * @param document The DOM document
     *
     * @return this (for method chaining purposes)
     *
     * @deprecated since 5.0.  Use one of the other methods for passing mapping source(s).
     */
    @Deprecated
    public MetadataSources addDocument(Document document) {
        final Origin origin = new Origin(SourceType.DOM, Origin.UNKNOWN_FILE_PATH);
        getXmlBindingsForWrite().add(new JaxpSourceXmlSource(origin, new DOMSource(document))
                .doBind(getXmlMappingBinderAccess().getMappingBinder()));
        return this;
    }

    /**
     * Read all mappings from a jar file.
     * <p/>
     * Assumes that any file named <tt>*.hbm.xml</tt> is a mapping document.
     *
     * @param jar a jar file
     *
     * @return this (for method chaining purposes)
     */
    public MetadataSources addJar(File jar) {
        if (LOG.isDebugEnabled()) {
            LOG.debugf("Seeking mapping documents in jar file : %s", jar.getName());
        }
        final Origin origin = new Origin(SourceType.JAR, jar.getAbsolutePath());
        try {
            JarFile jarFile = new JarFile(jar);
            final boolean TRACE = LOG.isTraceEnabled();
            try {
                Enumeration jarEntries = jarFile.entries();
                while (jarEntries.hasMoreElements()) {
                    final ZipEntry zipEntry = (ZipEntry) jarEntries.nextElement();
                    if (zipEntry.getName().endsWith(".hbm.xml")) {
                        if (TRACE) {
                            LOG.tracef("found mapping document : %s", zipEntry.getName());
                        }
                        getXmlBindingsForWrite().add(new JarFileEntryXmlSource(origin, jarFile, zipEntry)
                                .doBind(getXmlMappingBinderAccess().getMappingBinder()));
                    }
                }
            } finally {
                try {
                    jarFile.close();
                } catch (Exception ignore) {
                }
            }
        } catch (IOException e) {
            throw new MappingNotFoundException(e, origin);
        }
        return this;
    }

    private <Binding> List getXmlBindingsForWrite() {
        if (xmlBindings == null) {
            xmlBindings = new ArrayList<>();
        }
        return xmlBindings;
    }

    /**
     * Read all mapping documents from a directory tree.
     * <p/>
     * Assumes that any file named <tt>*.hbm.xml</tt> is a mapping document.
     *
     * @param dir The directory
     *
     * @return this (for method chaining purposes)
     *
     * @throws org.hibernate.MappingException Indicates problems reading the jar file or
     * processing the contained mapping documents.
     */
    public MetadataSources addDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files != null && files.length > 0) {
            for (File file : files) {
                if (file.isDirectory()) {
                    addDirectory(file);
                } else if (file.getName().endsWith(".hbm.xml")) {
                    addFile(file);
                }
            }
        }
        return this;
    }

}