Hello,
I have been using the Hibernate annotations beta6 and noticed that I had to configure each class I annotated using the standard
<mapping class="package.classname"/>
mechanism. The thing is since I no longer have hbm files I thought it would be nice if I could pass in a pattern that hibernate could use to detect the classes I annotated. This way I would be able to modularize my code more easily by simple specifing a pattern that would search the classpath for my files. In my case I keep all of my entity classes in a "model" package.
I found that I was able to do this by simple modifing the org.hibernate.cfg.AnnotationConfiguration class. I mostly changed the
parseMappingElement(Element subelement, String name) method but had to make a few additions to the
addAnnotatedClass(Class persistentClass) method as well to make sure any classes that made it into the
Quote:
annotatedClasses
List where annotated correctly.
Here is the code for the relevant methods
protected void parseMappingElement(Element subelement, String name)
{
Attribute rsrc = subelement.attribute("resource");
Attribute file = subelement.attribute("file");
Attribute jar = subelement.attribute("jar");
Attribute pckg = subelement.attribute("package");
Attribute clazz = subelement.attribute("class");
if (rsrc != null) {
log.debug(name + "<-" + rsrc);
addResource(rsrc.getValue());
}
else if (jar != null) {
log.debug(name + "<-" + jar);
addJar(new File(jar.getValue()));
}
else if (file != null) {
log.debug(name + "<-" + file);
addFile(file.getValue());
}
else if (pckg != null) {
log.debug(name + "<-" + pckg);
addPackage(pckg.getValue());
}
else if (clazz != null) {
log.debug(name + "<-" + clazz);
Class loadedClass = null;
List<Class> annotatedClazzes = new ArrayList<Class>();
try {
// clazzValue should always end in ".class" for pattern matching
// for annotated files
String clazzValue = clazz.getValue();
try {
// Try the class as a regular class name first
loadedClass = ReflectHelper.classForName(clazzValue);
annotatedClazzes.add(loadedClass);
}
catch (Exception ex) {
// If it fails check to see if it is a pattern
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// See if the BASE_PACKAGE property was set to enable the
// shortcut for getting a classes package
String basePackage = getProperties().getProperty(
BASE_PACKAGE);
// Get resources matching the class pattern
Resource[] resources = resolver.getResources(clazzValue);
// Check each resource returned from the
// PathMatchingResourcePatternResolver
String finalClassName;
File f;
for (Resource r : resources) {
finalClassName = null;
f = r.getFile();
// Make sure the resource is a file
if (!f.isDirectory()) {
// Get absolute path
String className = f.getAbsolutePath();
// Remove file extension and replace all '\' with
// '.'
int dotIndex = className.lastIndexOf('.');
if (dotIndex != -1) {
className = className.substring(0, dotIndex);
className = className.replaceAll("\\\\", ".");
}
// If basePackage is specified use shortcut to find
// package name
if (basePackage != null) {
dotIndex = className.indexOf(basePackage);
if (dotIndex != -1) {
finalClassName = className
.substring(dotIndex);
}
}
// otherwise do it the hard way removing one folder
// at a time until a full class name is found
// or there are no more folders
else {
Class c = null;
dotIndex = 0;
// check to see if the className is a real class
while ((c == null) && (dotIndex != -1)) {
try {
className = className
.substring(dotIndex);
ReflectHelper.classForName(className);
}
// if it is not found remove a folder from
// the front of the className and continue
catch (ClassNotFoundException cnf) {
dotIndex = className.indexOf(".") + 1;
}
}
finalClassName = className;
}
}
// Get Class object if a class name was found for the
// resource
if (finalClassName != null) {
loadedClass = ReflectHelper
.classForName(finalClassName);
annotatedClazzes.add(loadedClass);
}
}
}
// Loop through all of the classes found
for (Class annotatedClass : annotatedClazzes) {
addAnnotatedClass(annotatedClass);
}
}
catch (ClassNotFoundException cnf) {
throw new MappingException(
"Unable to load class declared as <mapping class=\""
+ clazz.getValue()
+ "\"/> in the configuration:", cnf);
}
catch (NoClassDefFoundError ncdf) {
throw new MappingException(
"Unable to load class declared as <mapping class=\""
+ clazz.getValue()
+ "\"/> in the configuration:", ncdf);
}
// Handle IOException's that may occur while retrieving resources
catch (IOException io) {
throw new MappingException(
"Unable to load class declared as <mapping class=\""
+ clazz.getValue()
+ "\"/> in the configuration:", io);
}
}
else {
throw new MappingException(
"<mapping> element in configuration specifies no attributes");
}
}
public AnnotationConfiguration addAnnotatedClass(Class persistentClass)
throws MappingException {
// Make sure the persistentClass is not an interface and
// make sure the Class is not already in the annotatedClasses List
// before adding it
if ((!persistentClass.isInterface())
&& (!annotatedClasses.contains(persistentClass))) {
// Make sure the provided class has one of the required annotations
boolean hasRequiredAnnotation = false;
for (Class annotation : REQUIRED_ANNOTATIONS) {
hasRequiredAnnotation = (hasRequiredAnnotation || persistentClass
.isAnnotationPresent(annotation));
}
if (hasRequiredAnnotation) {
try {
if (persistentClass.isAnnotationPresent(Entity.class)) {
annotatedClassEntities.put(persistentClass.getName(),
persistentClass);
}
annotatedClasses.add(persistentClass);
}
catch (MappingException me) {
log.error("Could not compile the mapping annotations", me);
throw me;
}
}
}
return this;
}
I also had to add a couple instance variables.
public static final Class[] REQUIRED_ANNOTATIONS = new Class[] {
Entity.class, Embeddable.class, EmbeddableSuperclass.class};
public static final String BASE_PACKAGE = "base.package";
The REQUIRED_ANNOTATIONS array is used to specify the annotations a class can have. If a class doesn't have one of these it isn't processed.
The BASE_PACKAGE variable is a new property I needed. It can be used to speed up the computation of a classes package name from a resource path. It is not required to be specified in the hibernate.cfg.xml file, however.
Here is an example hibernate.cfg.xml using the new mechanism.
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.url">
@hibernate.connection.url@
</property>
<property name="show_sql">
@hibernate.show_sql@
</property>
<property name="dialect">
@hibernate.dialect@
</property>
<property name="query.factory_class">
org.hibernate.hql.classic.ClassicQueryTranslatorFactory
</property>
<property name="hibernate.connection.username">
@hibernate.connection.username@
</property>
<property name="hibernate.connection.password">
@hibernate.connection.password@
</property>
<property name="hibernate.connection.driver_class">
@hibernate.connection.driver_class@
</property>
<property name="hibernate.default_schema">
@hibernate.connection.username@
</property>
<property name="base.package">org</property>
<mapping class="classpath*:**/*.class" />
</session-factory>
</hibernate-configuration>
Notice the base.package property is set to the first package folder and
that the class mapping specifies an Ant path instead of a specific class name.
I am currently working with Spring also and so I imported to classes
org.springframework.core.io.Resource;
org.springframework.core.io.support.PathMatchingResourcePatternResolver;
Resource is returned by the PathMatchingResourcePatternResolver class. The PathMatchingResourcePatternResolver class is kind of a utility class almost and did the job of retrieving all of the resources in the classpath if an Ant pattern was entered instead of a specific class. This dependency could probably be removed if another replacement was found to do the pattern matching. PathMatchingResourcePatternResolver uses the org.springframework.util.AntPathMatcher to do the pattern matching.
I have been using this for a couple of days now and it has worked beautifully and it would be great if this kind of functionality could be available in a future release of Hibernate's annotations.
Thanks to the Annotation team for there great work.