001/*
002 * Copyright 2002-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.orm.hibernate3.annotation;
018
019import java.io.IOException;
020import java.util.Set;
021import java.util.TreeSet;
022import javax.persistence.Embeddable;
023import javax.persistence.Entity;
024import javax.persistence.MappedSuperclass;
025
026import org.hibernate.HibernateException;
027import org.hibernate.MappingException;
028import org.hibernate.cfg.Configuration;
029
030import org.springframework.context.ResourceLoaderAware;
031import org.springframework.core.io.Resource;
032import org.springframework.core.io.ResourceLoader;
033import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
034import org.springframework.core.io.support.ResourcePatternResolver;
035import org.springframework.core.io.support.ResourcePatternUtils;
036import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
037import org.springframework.core.type.classreading.MetadataReader;
038import org.springframework.core.type.classreading.MetadataReaderFactory;
039import org.springframework.core.type.filter.AnnotationTypeFilter;
040import org.springframework.core.type.filter.TypeFilter;
041import org.springframework.util.ClassUtils;
042
043/**
044 * Subclass of Spring's standard LocalSessionFactoryBean for Hibernate,
045 * supporting annotation metadata for mappings.
046 *
047 * <p>Example for an AnnotationSessionFactoryBean bean definition:
048 *
049 * <pre class="code">
050 * &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
051 *   &lt;property name="dataSource" ref="dataSource"/&gt;
052 *   &lt;property name="annotatedClasses"&gt;
053 *     &lt;list&gt;
054 *       &lt;value&gt;test.package.Foo&lt;/value&gt;
055 *       &lt;value&gt;test.package.Bar&lt;/value&gt;
056 *     &lt;/list&gt;
057 *   &lt;/property&gt;
058 * &lt;/bean&gt;</pre>
059 *
060 * Or when using classpath scanning for autodetection of entity classes:
061 *
062 * <pre class="code">
063 * &lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
064 *   &lt;property name="dataSource" ref="dataSource"/&gt;
065 *   &lt;property name="packagesToScan" value="test.package"/&gt;
066 * &lt;/bean&gt;</pre>
067 *
068 * <p>Requires Hibernate 3.6.x, as of Spring 4.0.
069 *
070 * @author Juergen Hoeller
071 * @since 1.2.2
072 * @see #setDataSource
073 * @see #setHibernateProperties
074 * @see #setAnnotatedClasses
075 * @see #setAnnotatedPackages
076 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
077 */
078@Deprecated
079public class AnnotationSessionFactoryBean extends org.springframework.orm.hibernate3.LocalSessionFactoryBean
080                implements ResourceLoaderAware {
081
082        private static final String RESOURCE_PATTERN = "/**/*.class";
083
084        private static final String PACKAGE_INFO_SUFFIX = ".package-info";
085
086
087        private Class<?>[] annotatedClasses;
088
089        private String[] annotatedPackages;
090
091        private String[] packagesToScan;
092
093        private TypeFilter[] entityTypeFilters = new TypeFilter[] {
094                        new AnnotationTypeFilter(Entity.class, false),
095                        new AnnotationTypeFilter(Embeddable.class, false),
096                        new AnnotationTypeFilter(MappedSuperclass.class, false),
097                        new AnnotationTypeFilter(org.hibernate.annotations.Entity.class, false)};
098
099        private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
100
101
102        /**
103         * Specify annotated classes, for which mappings will be read from
104         * class-level annotation metadata.
105         * @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class)
106         */
107        public void setAnnotatedClasses(Class<?>... annotatedClasses) {
108                this.annotatedClasses = annotatedClasses;
109        }
110
111        /**
112         * Specify the names of annotated packages, for which package-level
113         * annotation metadata will be read.
114         * @see org.hibernate.cfg.Configuration#addPackage(String)
115         */
116        public void setAnnotatedPackages(String... annotatedPackages) {
117                this.annotatedPackages = annotatedPackages;
118        }
119
120        /**
121         * Specify packages to search using Spring-based scanning for entity classes in
122         * the classpath. This is an alternative to listing annotated classes explicitly.
123         * <p>Default is none. Specify packages to search for autodetection of your entity
124         * classes in the classpath. This is analogous to Spring's component-scan feature
125         * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
126         */
127        public void setPackagesToScan(String... packagesToScan) {
128                this.packagesToScan = packagesToScan;
129        }
130
131        /**
132         * Specify custom type filters for Spring-based scanning for entity classes.
133         * <p>Default is to search all specified packages for classes annotated with
134         * {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
135         * or {@code @javax.persistence.MappedSuperclass}, as well as for
136         * Hibernate's special {@code @org.hibernate.annotations.Entity}.
137         * @see #setPackagesToScan
138         */
139        public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
140                this.entityTypeFilters = entityTypeFilters;
141        }
142
143        @Override
144        public void setResourceLoader(ResourceLoader resourceLoader) {
145                this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
146        }
147
148
149        /**
150         * Reads metadata from annotated classes and packages into the
151         * AnnotationConfiguration instance.
152         */
153        @Override
154        protected void postProcessMappings(Configuration config) throws HibernateException {
155                if (this.annotatedClasses != null) {
156                        for (Class<?> annotatedClass : this.annotatedClasses) {
157                                config.addAnnotatedClass(annotatedClass);
158                        }
159                }
160                if (this.annotatedPackages != null) {
161                        for (String annotatedPackage : this.annotatedPackages) {
162                                config.addPackage(annotatedPackage);
163                        }
164                }
165                scanPackages(config);
166        }
167
168        /**
169         * Perform Spring-based scanning for entity classes.
170         * @see #setPackagesToScan
171         */
172        protected void scanPackages(Configuration config) {
173                if (this.packagesToScan != null) {
174                        Set<String> classNames = new TreeSet<String>();
175                        Set<String> packageNames = new TreeSet<String>();
176                        try {
177                                for (String pkg : this.packagesToScan) {
178                                        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
179                                                        ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
180                                        Resource[] resources = this.resourcePatternResolver.getResources(pattern);
181                                        MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
182                                        for (Resource resource : resources) {
183                                                if (resource.isReadable()) {
184                                                        MetadataReader reader = readerFactory.getMetadataReader(resource);
185                                                        String className = reader.getClassMetadata().getClassName();
186                                                        if (matchesEntityTypeFilter(reader, readerFactory)) {
187                                                                classNames.add(className);
188                                                        }
189                                                        else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
190                                                                packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
191                                                        }
192                                                }
193                                        }
194                                }
195                        }
196                        catch (IOException ex) {
197                                throw new MappingException("Failed to scan classpath for unlisted classes", ex);
198                        }
199                        try {
200                                for (String className : classNames) {
201                                        config.addAnnotatedClass(this.resourcePatternResolver.getClassLoader().loadClass(className));
202                                }
203                                for (String packageName : packageNames) {
204                                        config.addPackage(packageName);
205                                }
206                        }
207                        catch (ClassNotFoundException ex) {
208                                throw new MappingException("Failed to load annotated classes from classpath", ex);
209                        }
210                }
211        }
212
213        /**
214         * Check whether any of the configured entity type filters matches
215         * the current class descriptor contained in the metadata reader.
216         */
217        private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
218                if (this.entityTypeFilters != null) {
219                        for (TypeFilter filter : this.entityTypeFilters) {
220                                if (filter.match(reader, readerFactory)) {
221                                        return true;
222                                }
223                        }
224                }
225                return false;
226        }
227
228}