/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package reportgen.cores.ejb;
import reportgen.cores.ejb.annotations.DefineQueryProperty;
import reportgen.cores.ejb.annotations.DefineQueryEntity;
import reportgen.prototype.entity.QSQLBuilder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import org.jdom.Element;
import reportgen.prototype.context.Context;
import reportgen.prototype.context.group.ContextGroup;
import reportgen.prototype.entity.QEntity;
import reportgen.prototype.entity.QEntityProperty;
import reportgen.utils.ReportException;
import reportgen.utils.Atom;
import reportgen.utils.XML;
/**
* Объект - сущность, учавствующая в отчете.
* Содержит алиас, однозначно идентифицирующий сущность,
* ссылку на родительскую сущность и характер ее связи с родительской сущностью,
* Кроме того содержит список дочерних сущностей, выбранных для отчета.
* @author axe
*/
class QueryEntity extends QueryObject implements QEntity {
private static final String ATTR_CLASS = "class";
private static final String ATTR_IDENT = "ident";
private static final String LINKAGE = "linkage";
public static final String TAG = "entity";
private static final String SUBENTITY = "subentities";
private final Atom atom;
private final ContextGroup coreContextGroup;
private LinkageMode linkage;
private QueryEntity parent;
private List<QueryEntity> children = new ArrayList<QueryEntity>();
/**
* создает сущность на основе указанного класса
* Используется при создании корневых сущностей
* @param cls
*/
public QueryEntity(Class cls, ContextGroup coreContextGroup) {
super(cls, null);
atom = new Atom();
this.coreContextGroup = coreContextGroup;
}
/**
* создает сущность с заданными названием и описанием.
* используется при создании сущности на основе поля
* @param cls
* @param title
* @param desc
* @param fieldName
* @param linkedLocal
* @param parent
*/
private QueryEntity(Class cls, String title, String desc, String identificator,
LinkageMode backLinkage, QueryEntity linkedEntity,
ContextGroup coreContextGroup) {
super(cls, identificator, title, desc);
this.linkage = backLinkage;
this.parent = linkedEntity;
this.atom = new Atom();
this.coreContextGroup = coreContextGroup;
}
/**
* создает сущность с заданным идентификатором - для создания fwd сущностей
* @param cls
* @param fieldName
*/
private QueryEntity(Class cls, String identificator,
QueryEntity linkedEntity, ContextGroup coreContextGroup) {
super(cls, identificator);
this.parent = linkedEntity;
this.linkage = LinkageMode.backward;
this.atom = new Atom();
this.coreContextGroup = coreContextGroup;
}
public QueryEntity(Context context, Element element, ContextGroup coreContextGroup) throws ReportException {
this(context, element, null, coreContextGroup);
}
private QueryEntity(Context context, Element element,
QueryEntity linkedEntity, ContextGroup coreContextGroup) throws ReportException {
super(loadClass(XML.getStringAttribute(element, ATTR_CLASS)),
XML.getStringAttribute(element, ATTR_IDENT, null, false));
if(!element.getName().equals(TAG)) {
throw new ReportException("Имя тега не соответствует " + TAG);
}
this.coreContextGroup = coreContextGroup;
atom = new Atom(element, context);
this.parent = linkedEntity;
String linkageEl = XML.getStringAttribute(element, LINKAGE, null, false);
if(linkageEl == null) {
linkage = LinkageMode.backward;
} else {
try {
linkage = LinkageMode.valueOf(linkageEl);
} catch (IllegalArgumentException ex) {
throw new ReportException("Неизвестное значение аттрибута " + LINKAGE);
}
}
Element subentitiesRoot = element.getChild(SUBENTITY);
if(subentitiesRoot != null) {
List subentities = subentitiesRoot.getChildren();
for(int i=0; i<subentities.size(); i++) {
Element subentity = (Element) subentities.get(i);
if(!subentity.getName().equals(TAG)) {
continue;
}
QueryEntity subren = new QueryEntity(context, subentity, this, coreContextGroup);
if(subren.getLinkage() == null) {
throw new ReportException("Отсутствует аттрибут " + LINKAGE);
}
children.add(subren);
}
}
}
private static QueryEntitySet getEntitySet() {
return CoreFactoryEjb.getEntitySet();
}
private static Class loadClass(String name) {
Class cls = getEntitySet().getClassFromName(name);
if(cls != null) {
return cls;
} else {
int index = name.lastIndexOf(".");
if(index != -1) {
name = name.substring(index+1);
return loadClass(name);
}
}
throw new RuntimeException("Class '" + name + "' not found!");
}
@Override
public Element toXML() {
Element root = new Element(TAG);
atom.toXML(root);
root.setAttribute(ATTR_CLASS, getCls().getSimpleName());
String ident = getIdentification();
if(ident != null) {
root.setAttribute(ATTR_IDENT, ident);
}
if(linkage != null) {
root.setAttribute(LINKAGE, linkage.toString());
}
if(children.size() > 0) {
Element subent = new Element("subentities");
for(int i=0; i<children.size(); i++) {
subent.addContent(children.get(i).toXML());
}
root.addContent(subent);
}
return root;
}
@Override
public ContextGroup getContextGroup() {
return coreContextGroup;
}
@Override
public String toString() {
return super.toString() + "(id:"+ atom + ")";
}
/**
* Возвращает тип связи с линкованной сущностью
* @return true связан обратной ссылкой с родительской сущностью, false - прямой
*/
public LinkageMode getLinkage() {
return linkage;
}
@Override
public boolean isJoined() {
return linkage != null
&& linkage == LinkageMode.join_forward;
}
@Override
public void setJoined(boolean joined) {
if(linkage == null) {
throw new RuntimeException("Данная сущность не может быть связана в режиме JOIN, "
+ "поскольку является корневой сущностью");
}
if(!joined) {
if(linkage == LinkageMode.join_forward) {
linkage = LinkageMode.forward;
} else {
throw new RuntimeException("Данная сущность не может быть связана в режиме JOIN");
}
} else {
if(linkage == LinkageMode.forward) {
linkage = LinkageMode.join_forward;
} else {
throw new RuntimeException("Данная сущность не может быть связана в режиме JOIN");
}
}
}
/**
* Является ли сущность "опциональной" в запросе.
* Вернет тру если сущность или ее предок заджойнены
* @return
*/
@Override
public boolean isJoinedAnywere() {
return isJoined()
|| (parent != null && parent.isJoinedAnywere());
}
/**
* не все сущности являются bidirectional
* Селект для unidirectional в обратном направлении возможен, а джойн - нет
* @return
*/
@Override
public boolean canBeJoined() {
if(linkage == LinkageMode.backward || getParentEntity() == null) {
return false;
}
return isNullable(getParentEntity().getCls(), getIdentification());
}
private static boolean isNullable(Class parentClazz, String ident) {
try {
Field f = parentClazz.getDeclaredField(ident);
Column columnAnnot = f.getAnnotation(Column.class);
if(columnAnnot == null) {
JoinColumn jColumnAnnot = f.getAnnotation(JoinColumn.class);
if(jColumnAnnot == null) {
throw new RuntimeException("No column annotation on field '"
+ ident + "' at " + parentClazz.getName());
} else if(!jColumnAnnot.nullable()) {
return false;
}
} else if(!columnAnnot.nullable()) {
return false;
}
} catch (NoSuchFieldException ex) {
if(parentClazz.getSuperclass() != null) {
return isNullable(parentClazz.getSuperclass(), ident);
}
throw new RuntimeException(ex);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return true;
}
@Override
public Atom getAtom() {
return atom;
}
/**
* Возвращает алиас сущности
* @return
*/
@Override
public String getAlias() {
return "a" + atom;
}
/**
* Родительская сущность
* @return
*/
@Override
public QueryEntity getParentEntity() {
return parent;
}
/**
* Выдает список сущностей выбранных пользователем для отчета
* @return
*/
@Override
public QueryEntity[] getSelectedEntities() {
if(children.size() == 0) {
return null;
}
QueryEntity[] array = new QueryEntity[children.size()];
children.toArray(array);
return array;
}
@Override
public void buildFlatList(List<QEntity> ent) {
ent.add(this);
for(int i=0; i<children.size(); i++) {
children.get(i).buildFlatList(ent);
}
}
/**
* Добавляет сущность в список выбранных сущностей
* @param child
*/
@Override
public void addSelectedEntity(int index) {
QueryEntity[] lst = getAvialiableEntities();
if(lst == null || index<0 || index >= lst.length) {
throw new IllegalArgumentException("Out of bounds:" + index);
}
QueryEntity selected = lst[index];
selected.reReadClassInfo();
children.add(selected);
}
/**
* удаляет сущность из списка выбранных сущностей
* @param ren
*/
@Override
public void removeSelectedEntity(int index) {
children.remove(index);
}
@Override
public QSQLBuilder getSQLBuilder(Set<QEntityProperty> props) {
return new QuerySQLBuilder(this, props);
}
/**
* возвращает список связанных сущностей, доступных для выборки
* включая сущности с обратной связью
* @return
*/
@Override
public QueryEntity[] getAvialiableEntities() {
return getAvialiableEntities(getCls(), this, coreContextGroup);
}
private static QueryEntity[] getAvialiableEntities(Class cls,
QueryEntity thisEntity, ContextGroup coreContextGroup) {
LinkedList<QueryEntity> res = new LinkedList<QueryEntity>();
if(cls == null || cls == Object.class) {
return null;
}
//children entities - LinkageMode.fwd
Field[] f = cls.getDeclaredFields();
for(int i=0; i<f.length; i++) {
DefineQueryProperty gen = f[i].getAnnotation(DefineQueryProperty.class);
Class c = f[i].getType();
if(!c.isAnnotationPresent(Entity.class)
|| gen == null) {
continue;
}
res.add(new QueryEntity(c, gen.title(), gen.desc(), f[i].getName(),
LinkageMode.forward, thisEntity, coreContextGroup));
}
//backlinked
LinkedReportEntity[] linked = getEntitySet().getLinkedClasses(cls);
if(linked != null) {
for (int i = 0; i < linked.length; i++) {
LinkedReportEntity lren = linked[i];
res.add(new QueryEntity(lren.cls, lren.field, thisEntity, coreContextGroup));
}
}
QueryEntity[] superEntities = getAvialiableEntities(cls.getSuperclass(),
thisEntity, coreContextGroup);
if(superEntities != null) {
for(QueryEntity qes: superEntities) {
res.add(qes);
}
}
if(res.size() == 0) {
return null;
}
QueryEntity resArray[] = new QueryEntity[res.size()];
res.toArray(resArray);
return resArray;
}
@Override
public List<QEntityProperty> getAvialiableProperties() {
return getAvialiableProperties(getCls(), "", this);
}
private List<QEntityProperty> getAvialiableProperties(Class objClass,
String path, QueryEntity parent) {
List<QEntityProperty> res = new ArrayList<QEntityProperty>();
res.addAll(getFields(objClass, path, parent));
res.addAll(getMethods(objClass, path, parent));
return res;
}
private List<QEntityProperty> getFields(Class objClass, String path, QueryEntity parent) {
List<QEntityProperty> res = new ArrayList<QEntityProperty>();
//super class properties
Class superClass = objClass.getSuperclass();
if(superClass != null
&& superClass.isAnnotationPresent(DefineQueryEntity.class)) {
res.addAll(getFields(superClass, path, parent));
}
Object[] fields = objClass.getDeclaredFields();
ArrayList<Field> embedded = new ArrayList<Field>();
for(int i=0; i<fields.length; i++) {
Field field = (Field) fields[i];
DefineQueryProperty gen = field.getAnnotation(DefineQueryProperty.class);
if(gen == null) {
continue;
} else if(gen.embedded()) {
embedded.add(field);
}
QEntityProperty qep = null;
Class type = field.getType();
String name = path + field.getName();
if(type.isAnnotationPresent(Entity.class)) {
qep = new QueryEntityObjectField(parent, type, gen.title(), gen.desc(), name);
} else {
qep = new QueryEntityField(parent, type, gen.title(), gen.desc(), name);
}
res.add(qep);
}
//embedded properties
for(int i=0; i<embedded.size(); i++) {
Field field = embedded.get(i);
String newPath = path + field.getName() + ".";
res.addAll(getFields(field.getType(), newPath, parent));
}
return res;
}
private List<QEntityProperty> getMethods(Class objClass, String path, QueryEntity parent) {
List<QEntityProperty> res = new ArrayList<QEntityProperty>();
Object[] methods = objClass.getDeclaredMethods();
//super class properties
Class superClass = objClass.getSuperclass();
if(superClass != null
&& superClass.isAnnotationPresent(DefineQueryEntity.class)) {
res.addAll(getMethods(superClass, path, parent));
}
ArrayList<Method> embedded = new ArrayList<Method>();
for(int i=0; i<methods.length; i++) {
Method method = (Method) methods[i];
DefineQueryProperty gen = method.getAnnotation(DefineQueryProperty.class);
if(gen == null) {
continue;
} else if(gen.embedded()) {
embedded.add(method);
continue;
}
Class type = method.getReturnType();
if(type.isAnnotationPresent(Entity.class)) {
throw new RuntimeException("Аннотирован метод класса " + method.getDeclaringClass()
+ " представляющий не встроенную сущность " + method.getName());
}
QEntityProperty qep =new QueryEntityMethod(parent, type,
gen.title(), gen.desc(), path + method.getName());
res.add(qep);
}
//embedded properties
for(int i=0; i<embedded.size(); i++) {
Method method = embedded.get(i);
String newPath = path + method.getName() + ".";
Class cls = method.getReturnType();
res.addAll(getMethods(cls, newPath, parent));
}
return res;
}
/**
* Объект считается тем же, если совпадают
* - класс,
* - идентификатор,
* - тайтл,
* - описание
* - алиас
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final QueryEntity other = (QueryEntity) obj;
if (!this.atom.equals(other.atom)) {
return false;
}
return super.equals(obj);
}
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + this.atom.hashCode();
return hash * super.hashCode();
}
}