package net.sourceforge.javautil.database.jpa.xml;
import java.lang.annotation.Annotation;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import org.xml.sax.Attributes;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.reflection.ObjectVisitorBase;
import net.sourceforge.javautil.common.reflection.cache.ClassCache;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
import net.sourceforge.javautil.common.reflection.cache.ClassProperty;
import net.sourceforge.javautil.common.xml.XMLDocument;
import net.sourceforge.javautil.common.xml.XMLDocumentElement;
import net.sourceforge.javautil.common.xml.XMLDocumentElementBase;
import net.sourceforge.javautil.common.xml.XMLDocumentElementDefinition;
import net.sourceforge.javautil.common.xml.XMLDocumentElementNoOp;
import net.sourceforge.javautil.common.xml.XMLTranslationGuideBase;
import net.sourceforge.javautil.common.xml.XMLWriter;
/**
* This will translate special cases for {@link XMLDocument}'s that are also JPA entities.
* {@link ManyToMany} relationships are not currently supported.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: JPATranslationGuide.java 1149 2009-10-21 21:34:58Z ponderator $
*/
public class JPATranslationGuide extends XMLTranslationGuideBase {
protected final EntityManager em;
public JPATranslationGuide() { this(null); }
public JPATranslationGuide(EntityManager em) {
this.em = em;
}
public XMLDocumentElement createElement(XMLDocumentElement parent, XMLDocumentElementDefinition definition, Object instanceOverride) {
boolean translate = false;
ClassProperty type = definition.getProperty();
if (type != null) {
OneToOne oto = type.getAnnotation(OneToOne.class);
if (oto != null) {
if ("".equals( oto.mappedBy() )) translate = true; else return new XMLDocumentElementNoOp(parent, definition);
}
else if (type.getAnnotation(ManyToOne.class) != null) translate = true;
else if (type.getAnnotation(OneToMany.class) != null) return new XMLDocumentElementNoOp(parent, definition);
else if (type.getAnnotation(ManyToMany.class) != null) return new XMLDocumentElementNoOp(parent, definition);
if (type.getAnnotation(Transient.class) != null) return new XMLDocumentElementNoOp(parent, definition);
}
return translate ? new JPAEntityElementReference(parent, definition, instanceOverride) : null;
}
/**
* @param <T> The type of entity
* @param type The type's class
* @param id The id of the entity
* @return The instance that corresponds to the entity
*/
public Object getEntity (XMLDocumentElement parent, Class clazz, ClassProperty idProperty, Object id) {
if (em != null) {
Object entity = em.find(clazz, id);
if (entity != null) return entity;
}
EntityDocumentVisitor visitor = (EntityDocumentVisitor) parent.getDocumentRoot().getManager().accept(new EntityDocumentVisitor(clazz, idProperty, id));
return visitor.instance;
}
/**
* This is used to search an object for elements to resolve entities.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: JPATranslationGuide.java 1149 2009-10-21 21:34:58Z ponderator $
*/
public class EntityDocumentVisitor extends ObjectVisitorBase {
protected Class clazz;
protected Object id;
protected ClassProperty idProperty;
protected Object instance;
public EntityDocumentVisitor(Class clazz, ClassProperty idProperty, Object id, Class... exclude) {
super(exclude);
this.clazz = clazz;
this.id = id;
this.idProperty = idProperty;
}
@Override public boolean visitObject(Object instance, ClassProperty property) {
if (property != null && clazz.isAssignableFrom( instance.getClass() )) {
if (id.equals( idProperty.getValue(instance) )) {
this.instance = instance;
return false;
}
}
return true;
}
}
/**
* This represents a complex type that is also a JPA {@link Entity} and
* will be translated using its {@link Id} property.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: JPATranslationGuide.java 1149 2009-10-21 21:34:58Z ponderator $
*/
protected class JPAEntityElementReference extends XMLDocumentElementBase {
protected Object id;
protected ClassDescriptor desc;
protected ClassProperty idProperty;
protected Object instance;
public JPAEntityElementReference(XMLDocumentElement parent, XMLDocumentElementDefinition definition, Object instance) {
super(parent, definition);
this.desc = ClassCache.getFor(definition.getTagType());
this.idProperty = desc.getProperty(Id.class);
this.instance = instance;
if (this.instance != null) {
this.id = idProperty.getValue(instance);
}
}
public void appendContents(char[] contents, int offset, int length, boolean cdata, boolean ignorable) {}
public void applyAttributes(Attributes attributes) {
for (int a=0; a<attributes.getLength(); a++) {
String name = attributes.getLocalName(a);
if ("id".equals(name)) {
instance = getEntity(parent, definition.getTagType(), idProperty,
ReflectionUtil.coerceString(idProperty.getType(), attributes.getValue(a)));
return;
}
}
throw new IllegalStateException("Id not available for entity translation: " + definition.getTagName() + "/" + definition.getTagType());
}
public void endElement(XMLDocumentElement element) {
parent.setProperty(definition.getName(), instance);
}
public XMLDocumentElement startElement(String namespace, String prefix, String name) { return null; }
public void write(XMLWriter writer) {
if (id == null) throw new IllegalArgumentException("Cannot translate entities that do not have an id");
writer.startElement(getDefinition().getTagName());
writer.writeAttribute(null, "id", ReflectionUtil.coerce(String.class, idProperty.getValue(instance)));
writer.endElement(getDefinition().getTagName());
}
}
}