/*
* Copyright (c) 2010-2013 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.repo.sql.query.definition;
import com.evolveum.midpoint.repo.sql.data.common.ObjectReference;
import com.evolveum.midpoint.repo.sql.data.common.RObject;
import com.evolveum.midpoint.repo.sql.data.common.any.RAssignmentExtension;
import com.evolveum.midpoint.repo.sql.data.common.embedded.RPolyString;
import com.evolveum.midpoint.repo.sql.util.ClassMapper;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.Index;
import javax.persistence.*;
import javax.xml.namespace.QName;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
/**
* @author lazyman
*/
public class ClassDefinitionParser {
private static final Trace LOGGER = TraceManager.getTrace(ClassDefinitionParser.class);
public <T extends RObject> EntityDefinition parseObjectTypeClass(Class<T> type) {
ObjectTypes objectType = ClassMapper.getObjectTypeForHQLType(type);
QName jaxbName = objectType.getQName();
Class jaxbType = objectType.getClassDefinition();
EntityDefinition entityDefinition = new EntityDefinition(jaxbName, jaxbType, type.getSimpleName(), type);
updateEntityDefinition(entityDefinition);
return entityDefinition;
}
private void updateEntityDefinition(EntityDefinition entity) {
LOGGER.trace("### {}", new Object[]{entity.getJpaName()});
addVirtualDefinitions(entity);
Method[] methods = entity.getJpaType().getMethods();
entity.setEmbedded(entity.getJpaType().getAnnotation(Embeddable.class) != null);
for (Method method : methods) {
String methodName = method.getName();
if (Modifier.isStatic(method.getModifiers()) || "getClass".equals(methodName) ||
!methodName.startsWith("is") && !methodName.startsWith("get")) {
//it's not getter for property
continue;
}
if (method.isAnnotationPresent(Transient.class)) {
continue;
}
LOGGER.trace("# {}", new Object[]{methodName});
QName jaxbName = getJaxbName(method);
Class jaxbType = getJaxbType(method);
String jpaName = getJpaName(method);
Definition definition = createDefinition(jaxbName, jaxbType, jpaName, method);
entity.addDefinition(definition);
}
}
private void addVirtualDefinitions(EntityDefinition entityDef) {
Class jpaType = entityDef.getJpaType();
addVirtualDefinitionsForClass(entityDef, jpaType);
while ((jpaType = jpaType.getSuperclass()) != null) {
addVirtualDefinitionsForClass(entityDef, jpaType);
}
}
private void addVirtualDefinitionsForClass(EntityDefinition entityDef, Class jpaType) {
if (!jpaType.isAnnotationPresent(QueryEntity.class)) {
return;
}
QueryEntity qEntity = (QueryEntity) jpaType.getAnnotation(QueryEntity.class);
for (VirtualProperty property : qEntity.properties()) {
QName jaxbName = createQName(property.jaxbName());
VirtualPropertyDefinition def = new VirtualPropertyDefinition(jaxbName, property.jaxbType(),
property.jpaName(), property.jpaType());
def.setAdditionalParams(property.additionalParams());
entityDef.addDefinition(def);
}
for (VirtualReference reference : qEntity.references()) {
}
for (VirtualCollection collection : qEntity.collections()) {
QName jaxbName = createQName(collection.jaxbName());
VirtualCollectionDefinition def = new VirtualCollectionDefinition(jaxbName,
collection.jaxbType(), collection.jpaName(), collection.jpaType());
def.setAdditionalParams(collection.additionalParams());
updateCollectionDefinition(def, collection.collectionType(), jaxbName, collection.jpaName());
entityDef.addDefinition(def);
}
}
private QName createQName(JaxbName name) {
return new QName(name.namespace(), name.localPart());
}
private Definition createDefinition(QName jaxbName, Class jaxbType, String jpaName, AnnotatedElement object) {
Class jpaType = (object instanceof Class) ? (Class) object : ((Method) object).getReturnType();
Definition definition;
if (ObjectReference.class.isAssignableFrom(jpaType)) {
ReferenceDefinition refDef = new ReferenceDefinition(jaxbName, jaxbType, jpaName, jpaType);
definition = updateReferenceDefinition(refDef, object);
} else if (RAssignmentExtension.class.isAssignableFrom(jpaType)) {
definition = new AnyDefinition(jaxbName, jaxbType, jpaName, jpaType);
} else if (Set.class.isAssignableFrom(jpaType)) {
CollectionDefinition collDef = new CollectionDefinition(jaxbName, jaxbType, jpaName, jpaType);
updateCollectionDefinition(collDef, object, null, null);
definition = collDef;
} else if (isEntity(object)) {
EntityDefinition entityDef = new EntityDefinition(jaxbName, jaxbType, jpaName, jpaType);
if ("com.evolveum.midpoint.repo.sql.data.common.embedded".equals(jpaType.getPackage().getName())) {
updateEntityDefinition(entityDef);
}
definition = entityDef;
} else {
PropertyDefinition propDef = new PropertyDefinition(jaxbName, jaxbType, jpaName, jpaType);
definition = updatePropertyDefinition(propDef, object);
}
return definition;
}
private CollectionDefinition updateCollectionDefinition(CollectionDefinition definition, AnnotatedElement object,
QName jaxbName, String jpaName) {
Definition collDef;
if (object instanceof Method) {
Method method = (Method) object;
ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
Type type1 = type.getActualTypeArguments()[0];
Class clazz;
if (type1 instanceof Class) {
clazz = ((Class) type1);
} else {
clazz = (Class) ((ParameterizedType) type1).getRawType();
}
QName realJaxbName = getJaxbName(method);
Class jaxbType = getJaxbType(clazz);
String realJpaName = getJpaName(method);
collDef = createDefinition(realJaxbName, jaxbType, realJpaName, clazz);
} else {
Class clazz = (Class) object;
Class jaxbType = getJaxbType(clazz);
collDef = createDefinition(jaxbName, jaxbType, jpaName, clazz);
}
if (collDef instanceof EntityDefinition) {
updateEntityDefinition((EntityDefinition) collDef);
}
definition.setDefinition(collDef);
return definition;
}
private boolean isEntity(AnnotatedElement object) {
Class type = (object instanceof Class) ? (Class) object : ((Method) object).getReturnType();
if (RPolyString.class.isAssignableFrom(type)) {
//it's hibernate entity but from prism point of view it's property
return false;
}
return type.getAnnotation(Entity.class) != null || type.getAnnotation(Embeddable.class) != null;
}
private PropertyDefinition updatePropertyDefinition(PropertyDefinition definition, AnnotatedElement object) {
if (object.isAnnotationPresent(Lob.class)) {
definition.setLob(true);
}
if (object.isAnnotationPresent(Enumerated.class)) {
definition.setEnumerated(true);
}
//todo implement also lookup for @Table indexes
if (object.isAnnotationPresent(Index.class)) {
definition.setIndexed(true);
}
return definition;
}
private ReferenceDefinition updateReferenceDefinition(ReferenceDefinition definition, AnnotatedElement object) {
if (object.isAnnotationPresent(Embedded.class)) {
definition.setEmbedded(true);
}
return definition;
}
private QName getJaxbName(Method method) {
QName name = new QName(SchemaConstantsGenerated.NS_COMMON, getPropertyName(method.getName()));
if (method.isAnnotationPresent(JaxbName.class)) {
JaxbName jaxbName = method.getAnnotation(JaxbName.class);
name = new QName(jaxbName.namespace(), jaxbName.localPart());
}
return name;
}
private Class getJaxbType(Method method) {
return getJaxbType(method.getReturnType());
}
private Class getJaxbType(Class clazz) {
if (RObject.class.isAssignableFrom(clazz)) {
ObjectTypes objectType = ClassMapper.getObjectTypeForHQLType(clazz);
return objectType.getClassDefinition();
}
if (clazz.getAnnotation(JaxbType.class) != null) {
JaxbType type = (JaxbType) clazz.getAnnotation(JaxbType.class);
return type.type();
}
return clazz;
}
private String getJpaName(Method method) {
String methodName = method.getName();
return getPropertyName(methodName);
}
private String getPropertyName(String methodName) {
int startIndex = 3; //method name starts with "get"
if (methodName.startsWith("is")) {
startIndex = 2;
}
char first = Character.toLowerCase(methodName.charAt(startIndex));
return Character.toString(first) + StringUtils.substring(methodName, startIndex + 1, methodName.length());
}
}