/*
* Copyright (c) 2003-2005
* XDoclet Team
* All rights reserved.
*/
package org.xdoclet.plugin.hibernate;
import java.io.File;
import java.util.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.generama.JellyTemplateEngine;
import org.generama.QDoxCapableMetadataProvider;
import org.generama.WriterMapper;
import org.generama.defaults.XMLOutputValidator;
import org.xdoclet.plugin.hibernate.qtags.HibernateFilterParamTag;
import org.xdoclet.plugin.hibernate.qtags.HibernateTypedefParamTag;
import org.xdoclet.plugin.hibernate.qtags.TagLibrary;
import com.thoughtworks.qdox.model.AbstractJavaEntity;
import com.thoughtworks.qdox.model.BeanProperty;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
/**
* Plugin producing hibernate mapping declarations
*
* @author Konstantin Pribluda
* @author Anatol Pomozov
*/
public class HibernateMappingPlugin extends AbstractHibernatePlugin {
static final String TAG_PREFIX = "hibernate.";
static final Log log = LogFactory.getLog(HibernateMappingPlugin.class);
static final Map tagDispatch = new HashMap();
// list holding tag names defining property existence
static final List PROPERTY_TAGS = new ArrayList();
// list holding tag names defining property existence
static final List ALLOWED_IN_PROPERTIES_TAGS = new ArrayList();
// list holding tag names allowed in join tag
static final List ALLOWED_IN_JOIN_TAGS = new ArrayList();
// list holding tag names causing stop of recursive search for properties
static final List HIERARCHY_STOP_TAGS = new ArrayList();
// list holding tag names defining ID existence
static final List ID_TAGS = new ArrayList();
// list holding tag names defining ID existence
static final List VERSION_TAGS = new ArrayList();
static {
PROPERTY_TAGS.add("hibernate.property");
PROPERTY_TAGS.add("hibernate.many-to-one");
PROPERTY_TAGS.add("hibernate.one-to-one");
PROPERTY_TAGS.add("hibernate.component");
PROPERTY_TAGS.add("hibernate.dynamic-component");
PROPERTY_TAGS.add("hibernate.any");
PROPERTY_TAGS.add("hibernate.map");
PROPERTY_TAGS.add("hibernate.set");
PROPERTY_TAGS.add("hibernate.list");
PROPERTY_TAGS.add("hibernate.bag");
PROPERTY_TAGS.add("hibernate.idbag");
PROPERTY_TAGS.add("hibernate.array");
PROPERTY_TAGS.add("hibernate.primitive-array");
PROPERTY_TAGS.add("hibernate.key-property");
PROPERTY_TAGS.add("hibernate.key-many-to-one");
PROPERTY_TAGS.add("hibernate.parent");
HIERARCHY_STOP_TAGS.add("hibernate.class");
HIERARCHY_STOP_TAGS.add("hibernate.subclass");
HIERARCHY_STOP_TAGS.add("hibernate.joined-subclass");
HIERARCHY_STOP_TAGS.add("hibernate.union-subclass");
ID_TAGS.add("hibernate.id");
ID_TAGS.add("hibernate.composite-id");
VERSION_TAGS.add("hibernate.version");
VERSION_TAGS.add("hibernate.timestamp");
ALLOWED_IN_PROPERTIES_TAGS.add("hibernate.property");
ALLOWED_IN_PROPERTIES_TAGS.add("hibernate.many-to-one");
ALLOWED_IN_PROPERTIES_TAGS.add("hibernate.component");
ALLOWED_IN_PROPERTIES_TAGS.add("hibernate.dynamic-component");
ALLOWED_IN_JOIN_TAGS.add("hibernate.property");
ALLOWED_IN_JOIN_TAGS.add("hibernate.many-to-one");
ALLOWED_IN_JOIN_TAGS.add("hibernate.component");
ALLOWED_IN_JOIN_TAGS.add("hibernate.dynamic-component");
}
private boolean force = false;
private Stack componentsPrefixies = new Stack();
public HibernateMappingPlugin(JellyTemplateEngine jellyTemplateEngine,
QDoxCapableMetadataProvider metadataProvider, WriterMapper writerMapper) {
super(jellyTemplateEngine, metadataProvider, writerMapper);
setFileregex("\\.java");
setFilereplace("\\.hbm.xml");
setMultioutput(true);
Map dtds = new HashMap();
dtds.put("http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd",
getClass().getResource("dtd/hibernate-mapping-2.0.dtd"));
dtds.put("http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd",
getClass().getResource("dtd/hibernate-mapping-3.0.dtd"));
setOutputValidator(new XMLOutputValidator(dtds));
new TagLibrary(metadataProvider);
}
/**
* Get all candidates for properties tag
*/
public List getAlternateKeyProperties(JavaClass clazz, final String propertiesName) {
if (propertiesName == null) {
return null;
}
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, null, true, ALLOWED_IN_PROPERTIES_TAGS, retval);
CollectionUtils.filter(retval,
new Predicate() {
public boolean evaluate(Object object) {
HibernateProperty prop = (HibernateProperty) object;
DocletTag tag = getTagByNameList(prop.getEntity(), ALLOWED_IN_PROPERTIES_TAGS);
return tag != null && propertiesName.equalsIgnoreCase(tag.getNamedParameter("properties-name"));
}
});
return retval;
}
/**
* Get all candidates for join tag
*/
public List getJoinProperties(JavaClass clazz, final String joinName) {
if (joinName == null) {
return null;
}
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, null, true, ALLOWED_IN_JOIN_TAGS, retval);
CollectionUtils.filter(retval,
new Predicate() {
public boolean evaluate(Object object) {
HibernateProperty prop = (HibernateProperty) object;
DocletTag tag = getTagByNameList(prop.getEntity(), ALLOWED_IN_JOIN_TAGS);
return tag != null && joinName.equalsIgnoreCase(tag.getNamedParameter("join-name"));
}
});
return retval;
}
/**
* Get at least one doclet tag from javaEntity
* @param entity entity which have doclet tags
* @param tagNames list of possible tag names
* @return found doclet tag or null if there is no such tag
*/
public DocletTag getTagByNameList(AbstractJavaEntity entity, List tagNames) {
for (int i = 0; i < tagNames.size(); i++) {
String tag = (String) tagNames.get(i);
DocletTag docletTag = entity.getTagByName(tag);
if (docletTag != null) {
return docletTag;
}
}
return null;
}
/**
* provide list of properties candidating for class id
*
* @param clazz
* @return
*/
public List getClassId(JavaClass clazz) {
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, null, true, ID_TAGS, retval);
return retval;
}
/**
* provide list of hibernate properties for class. it could be getter, as
* well as field ( for direct property access )
*/
public List getClassProperties(JavaClass clazz) {
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, null, true, PROPERTY_TAGS, retval);
return retval;
}
/**
* Get list of properties for component class. Real class is calculated in following sequence.
* First tries to find class by its name if classname parameter is null or class not found
* @param componentClassName
* @param componentPropertyClass
* @return
*/
public List getComponentProperties(String componentClassName, JavaClass componentPropertyClass) {
JavaClass componentClass;
if (componentClassName != null) {
componentClass = getMetadata(componentClassName);
} else {
componentClass = componentPropertyClass;
}
return getClassProperties(componentClass);
}
/**
* Returns first argument wich is not empty or last argument if all are empty
*
* @param value1 first argument
* @param value2 second argument
* @return
*/
public String getFirstNonEmptyValue(String value1, String value2) {
return value1 != null && value1.trim().length() > 0 ? value1 : value2;
}
public void setForce(boolean force) {
this.force = force;
}
/**
* Return the instance of JavaClass by it's name
*/
public JavaClass getMetadata(final String className) {
if (className == null) {
throw new NullPointerException("Classname can't be null");
}
JavaClass javaClass = (JavaClass) CollectionUtils.find(getMetadata(),
new Predicate() {
public boolean evaluate(Object o) {
JavaClass cl = (JavaClass) o;
return cl.getFullyQualifiedName().equals(className);
}
});
if (javaClass == null) {
log.error("Sourcecode for class '" + className + "' not found by metadata povider");
throw new IllegalArgumentException("Class metadata for " + className + " not found");
}
return javaClass;
}
/**
* derive property name from metadata. strip / decapitalize
*/
public String getPropertyName(AbstractJavaEntity metadata) {
if (metadata instanceof JavaMethod) {
return ((JavaMethod) metadata).getPropertyName();
} else if (metadata instanceof JavaField) {
return metadata.getName();
}
// maybe throw exception?
return null;
}
/**
* provide list of valid property tags
*/
public List getPropertyTagList() {
return PROPERTY_TAGS;
}
/**
* provide list of valid property tags that can be used inside <properties> tag
*/
public List getTagListAllowedInProperties() {
return ALLOWED_IN_PROPERTIES_TAGS;
}
/**
* provide list of valid property tags that can be used inside <join> tag
*/
public List getTagListAllowedInJoin() {
return ALLOWED_IN_JOIN_TAGS;
}
/**
* provide list of hibernate properties for subclass. it could be getter, as
* well as field ( for direct property access ). we stop at hibernate.class
*/
public List getSubclassProperties(JavaClass clazz) {
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, HIERARCHY_STOP_TAGS, true, PROPERTY_TAGS, retval);
return retval;
}
/**
* provide list of subclasses for given class
*/
public List getSubclasses(JavaClass clazz, String tagName) {
List result = new ArrayList();
getSubclassesWithTagRecursive(result, clazz, tagName);
Collections.sort(result);
return result;
}
/**
* provide combined list of all the tags with given tag names
*/
public List getTags(AbstractJavaEntity metadata, Collection tagNames) {
List al = new ArrayList();
for (Iterator iter = tagNames.iterator(); iter.hasNext();) {
al.addAll(Arrays.asList(metadata.getTagsByName((String) iter.next())));
}
return al;
}
/**
* provide list of tags from pipe separated string
*/
public List getTags(AbstractJavaEntity metadata, String tagNames) {
return getTags(metadata, Arrays.asList(tagNames.split("\\|")));
}
public Collection getTypedefParams(JavaClass clazz, final String typedefName) {
if (typedefName == null) {
return null;
}
Collection typedefTags = new ArrayList();
typedefTags.addAll(Arrays.asList(clazz.getTagsByName("hibernate.typedef-param")));
CollectionUtils.filter(typedefTags,
new Predicate() {
public boolean evaluate(Object object) {
HibernateTypedefParamTag tag = (HibernateTypedefParamTag) object;
return typedefName.equals(tag.getTypedefName());
}
});
return typedefTags;
}
public Collection getFilterdefParams(JavaClass clazz, final String filterdefName) {
if (filterdefName == null) {
return null;
}
Collection filterdefTags = new ArrayList();
filterdefTags.addAll(Arrays.asList(clazz.getTagsByName("hibernate.filter-param", true)));
CollectionUtils.filter(filterdefTags,
new Predicate() {
public boolean evaluate(Object object) {
HibernateFilterParamTag tag = (HibernateFilterParamTag) object;
return filterdefName.equals(tag.getFilterdefName());
}
});
return filterdefTags;
}
/**
* provide list of properties candidating for version or timestamp
*/
public List getVersionOrTimestamp(JavaClass clazz) {
List retval = new ArrayList();
accumulatePropertiesRecursive(clazz, null, true, VERSION_TAGS, retval);
return retval;
}
/**
* dispatch qtag to correct jelly script. cache results statically
*/
public String dispatchTag(String tagName) {
String retval = (String) tagDispatch.get(tagName);
if (retval == null) {
// rig up correct script name from tag name
// first, kill hibernate
if (!tagName.startsWith(TAG_PREFIX)) {
return null;
}
// split on dash and capitalize first chars
String[] nameParts = tagName.substring(TAG_PREFIX.length()).split("-");
StringBuffer scriptName = new StringBuffer("/");
for (int i = 0; i < nameParts.length; i++) {
scriptName.append(Character.toUpperCase(nameParts[i].charAt(0)));
scriptName.append(nameParts[i].substring(1));
}
scriptName.append(".jelly");
retval = scriptName.toString();
log.debug("Dispatch tag " + tagName + " to script " + retval);
tagDispatch.put(tagName, retval);
}
return retval;
}
public boolean hasAtLeastOne(AbstractJavaEntity metadata, Collection tags) {
return getTags(metadata, tags).size() >= 1;
}
public boolean hasAtMostOne(AbstractJavaEntity metadata, Collection tags) {
return getTags(metadata, tags).size() <= 1;
}
public boolean hasOnlyOne(AbstractJavaEntity metadata, Collection tags) {
return getTags(metadata, tags).size() == 1;
}
/**
* whether we sould generate given class. we generate if class contains
* hibernate.class tag on it. class could be as well abstract, because real
* stuff lives in polymorphic subclasses
*/
public boolean shouldGenerate(Object metadata) {
JavaClass clazz = (JavaClass) metadata;
//Check if mapping up-to-date then skip generation
if (!force) {
String packagePath = getDestinationPackage(metadata).replace('.', '/');
File dir = new File(getDestdirFile(), packagePath);
String filename = getDestinationFilename(metadata);
File destFile = new File(dir, filename);
File sourceFile = new File(clazz.getSource().getURL().getFile());
if (destFile.exists() && sourceFile.lastModified() < destFile.lastModified()) {
return false;
}
}
// we refuse to generate if object is in restricted folder
if(!super.shouldGenerate(metadata)) {
return false;
}
boolean generate = clazz.getTagByName("hibernate.class") != null;
if (generate) {
System.out.println(" * Generate mapping for '" + clazz.getName() + "' entity");
}
return generate;
}
/**
* gather hibernate propertis from given class into list
*/
private void accumulateProperties(JavaClass clazz, Collection requieredTags, List accumulate) {
// walk through property getters
BeanProperty[] beanProperties = clazz.getBeanProperties();
HibernateProperty property;
for (int i = 0; i < beanProperties.length; i++) {
// property is ours, if we have at least one of designated property
// tags and there is accessor
if ((beanProperties[i] != null) && (beanProperties[i].getAccessor() != null) &&
!getTags(beanProperties[i].getAccessor(), requieredTags).isEmpty()) {
property = new HibernateProperty();
property.setName(beanProperties[i].getName());
property.setEntity(beanProperties[i].getAccessor());
// we do not specify access setting unless there is explicit
// setting
if(((DocletTag)getTags(beanProperties[i].getAccessor(),requieredTags).get(0)).getNamedParameter("access")!= null) {
property.setAccess(((DocletTag)getTags(beanProperties[i].getAccessor(),requieredTags).get(0)).getNamedParameter("access"));
}
if (!accumulate.contains(property)) {
accumulate.add(property);
}
}
}
JavaField[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
if (!getTags(fields[i], requieredTags).isEmpty()) {
property = new HibernateProperty();
property.setName(fields[i].getName());
property.setEntity(fields[i]);
// if no property access is specified, we shall use field
// else take whatever user says ( leave it to his discretion to
// use wrong specification )
if(((DocletTag)getTags(fields[i], requieredTags).get(0)).getNamedParameter("access") != null) {
property.setAccess(((DocletTag)getTags(fields[i], requieredTags).get(0)).getNamedParameter("access"));
} else {
property.setAccess("field");
}
if (!accumulate.contains(property)) {
accumulate.add(property);
}
}
}
}
/**
* recursive property retrival stopping at stop tag
* @param skipStopTags needs more explanation here. We need to skip checking on stop tags on the first method invocation
* (I mean when we didnt dive into recursion cycle yet) because it have already @hibernate.property tag (which is stop tag by itself)
*/
private void accumulatePropertiesRecursive(JavaClass clazz, Collection stopTags, boolean skipStopTags,
Collection requiredTags, List accumulate) {
// stop recursion?
if (!skipStopTags && stopTags != null && !getTags(clazz, stopTags).isEmpty()) {
// yep.
return;
}
accumulateProperties(clazz, requiredTags, accumulate);
//Look at subclass
JavaClass superclass = clazz.getSuperJavaClass();
if (superclass != null) {
accumulatePropertiesRecursive(superclass, stopTags, false, requiredTags, accumulate);
}
//Browse over all implemented interfaces
JavaClass[] ifaces = clazz.getImplementedInterfaces();
for (int i = 0; i < ifaces.length; i++) {
accumulatePropertiesRecursive(ifaces[i], stopTags, false, requiredTags, accumulate);
}
}
/**
* recursively retrieve list of derived classes with certain tag, cut search
* on found tag. also preveent double addition of subclass to the list (
* due to interface inheritance )
*/
private void getSubclassesWithTagRecursive(List found, JavaClass clazz, String tagName) {
JavaClass[] subclasses = clazz.getDerivedClasses();
for (int i = 0; i < subclasses.length; i++) {
if ((subclasses[i].getSuperJavaClass() == clazz) ||
Arrays.asList(subclasses[i].getImplementedInterfaces()).contains(clazz)) {
if (subclasses[i].getTagByName(tagName) != null) {
if(!found.contains(subclasses[i])) {
found.add(subclasses[i]);
}
} else {
getSubclassesWithTagRecursive(found, subclasses[i], tagName);
}
}
}
}
public void startComponent(String prefix) {
componentsPrefixies.push(prefix);
}
public void endComponent() {
componentsPrefixies.pop();
}
public String buildComponentColumnName(String columnName) {
//If columnName is null then we do not need add prefix
if (columnName == null) {
return null;
}
if (componentsPrefixies.isEmpty()) {
return columnName;
}
StringBuffer result = new StringBuffer();
for (Iterator iterator = componentsPrefixies.iterator(); iterator.hasNext();) {
String pr = (String) iterator.next();
if (pr != null) {
result.append(pr);
}
}
result.append(columnName);
return result.toString();
}
}