/**
* Copyright (C) 2012 the original author or authors.
*
* 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 co.jirm.mapper.definition;
import static co.jirm.core.util.JirmPrecondition.check;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.persistence.Column;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Version;
import co.jirm.mapper.SqlObjectConfig;
import co.jirm.mapper.converter.SqlParameterConverter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Optional;
public class SqlParameterDefinition {
private final String parameterName;
private final String sqlName;
private final Class<?> parameterType;
private final int order;
private final boolean id;
private final boolean version;
private final boolean generated;
private final Optional<Enumerated> enumerated;
private final Optional<SqlParameterObjectDefinition> objectDefinition;
private final SqlParameterConverter parameterConverter;
private SqlParameterDefinition(
SqlParameterConverter parameterConverter, String parameterName, String sqlName, Class<?> parameterType, int order,
boolean id, boolean version, boolean generated, Optional<Enumerated> enumerated,
Optional<SqlParameterObjectDefinition> objectDefinition) {
super();
this.parameterName = check.notNull(parameterName, "parameterName");
this.sqlName = check.notNull(sqlName, "sqlName");
this.parameterType = check.notNull(parameterType, "parameterType");
this.order = order;
this.id = id;
this.version = version;
this.enumerated = check.notNull(enumerated, "enumerated");
this.objectDefinition = check.notNull(objectDefinition, "objectDefinition");
this.parameterConverter = check.notNull(parameterConverter, "parameterConverter");
this.generated = generated;
}
static SqlParameterDefinition newSimpleInstance(
SqlParameterConverter parameterConverter,
String parameterName, Class<?> parameterType,
int order, String sqlName, boolean id, boolean version, boolean generated, Optional<Enumerated> enumerated) {
Optional<SqlParameterObjectDefinition> objectDefinition = Optional.absent();
return new SqlParameterDefinition(parameterConverter,
parameterName,
sqlName,
parameterType,
order,
id, version, generated, enumerated, objectDefinition);
}
static SqlParameterDefinition newComplexInstance(
SqlParameterConverter parameterConverter,
String parameterName,
@Nonnull
SqlParameterObjectDefinition objDef,
int order, String sqlName) {
Optional<SqlParameterObjectDefinition> objectDefinition = Optional.<SqlParameterObjectDefinition>of(objDef);
Class<?> parameterType = objectDefinition.get().getObjectDefintion().getObjectType();
boolean id = false;
boolean version = false;
boolean generated = false;
Optional<Enumerated> enumerated = Optional.absent();
return new SqlParameterDefinition(parameterConverter,
parameterName,
sqlName,
parameterType,
order,
id, version, generated, enumerated, objectDefinition);
}
public Object convertToSql(Object original) {
return this.parameterConverter.convertToSql(original, this);
}
static Map<String, SqlParameterDefinition> getSqlBeanParameters(Class<?> k, SqlObjectConfig config) {
Map<String, SqlParameterDefinition> parameters = new LinkedHashMap<String, SqlParameterDefinition>();
Constructor<?> cons[] = k.getDeclaredConstructors();
for (Constructor<?> c : cons) {
if (c.isAnnotationPresent(JsonCreator.class)) {
return getSqlBeanParametersFromJsonCreatorConstructor(c, config);
}
if (c.isAnnotationPresent(ConstructorProperties.class)) {
return getSqlBeanParametersFromConstructorProperties(c, config);
}
}
check.argument(! parameters.isEmpty(),
"No SQL columns/parameters found for: {}", k);
return parameters;
}
public Optional<SqlParameterObjectDefinition> getObjectDefinition() {
return objectDefinition;
}
static SqlParameterDefinition parameterDef(
SqlObjectConfig config,
Class<?> objectType,
String parameterName,
Class<?> parameterType, int order) {
final SqlParameterDefinition definition;
String sn = null;
ManyToOne manyToOne = getAnnotation(objectType, parameterName, ManyToOne.class);
if (manyToOne != null) {
Class<?> subK = checkNotNull(manyToOne.targetEntity(), "targetEntity not set");
JoinColumn joinColumn = getAnnotation(objectType, parameterName, JoinColumn.class);
SqlObjectDefinition<?> od = SqlObjectDefinition.fromClass(subK, config);
checkState( ! od.getIdParameters().isEmpty(), "No id parameters");
if (joinColumn != null)
sn = joinColumn.name();
if (sn == null)
sn = config.getNamingStrategy().propertyToColumnName(parameterName);
FetchType fetch = manyToOne.fetch();
int depth;
if (FetchType.LAZY == fetch) {
depth = 1;
}
else {
depth = config.getMaximumLoadDepth();
}
SqlParameterObjectDefinition sod = new SqlParameterObjectDefinition(od, depth);
definition = SqlParameterDefinition.newComplexInstance(config.getConverter(), parameterName, sod, order, sn);
}
else {
Column col = getAnnotation(objectType, parameterName, Column.class);
if (col != null && ! isNullOrEmpty(col.name()))
sn = col.name();
Id id = getAnnotation(objectType, parameterName, Id.class);
Version version = getAnnotation(objectType, parameterName, Version.class);
GeneratedValue generated = getAnnotation(objectType, parameterName, GeneratedValue.class);
Enumerated enumerated = getAnnotation(objectType, parameterName, Enumerated.class);
boolean idFlag = id != null;
boolean versionFlag = version != null;
boolean generatedFlag = generated != null;
if (sn == null)
sn = config.getNamingStrategy().propertyToColumnName(parameterName);
definition = SqlParameterDefinition.newSimpleInstance(config.getConverter(), parameterName,
parameterType, order, sn, idFlag, versionFlag, generatedFlag, Optional.fromNullable(enumerated));
}
return definition;
}
private static <T extends Annotation> T getAnnotation(Class<?> k, String value, Class<T> a) {
try {
Field f = k.getDeclaredField(value);
return f.getAnnotation(a);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
public String getParameterName() {
return parameterName;
}
public Class<?> getParameterType() {
return parameterType;
}
public int getOrder() {
return order;
}
public String sqlName() {
return sqlName;
}
public String sqlName(String prefix) {
if (prefix != null)
return prefix + "." + sqlName;
return sqlName;
}
public boolean isId() {
return id;
}
public boolean isComplex() {
return this.getObjectDefinition().isPresent();
}
public boolean isVersion() {
return version;
}
public boolean isGenerated() {
return generated;
}
public Optional<Enumerated> getEnumerated() {
return enumerated;
}
//@SuppressWarnings("unchecked")
public Optional<Object> valueFrom(Map<String,Object> m) {
Object o = m.get(this.getParameterName());
return Optional.fromNullable(o);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (id ? 1231 : 1237);
result = prime * result + ((objectDefinition == null) ? 0 : objectDefinition.hashCode());
result = prime * result + order;
result = prime * result + ((parameterName == null) ? 0 : parameterName.hashCode());
result = prime * result + ((parameterType == null) ? 0 : parameterType.hashCode());
result = prime * result + ((sqlName == null) ? 0 : sqlName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SqlParameterDefinition other = (SqlParameterDefinition) obj;
if (id != other.id)
return false;
if (objectDefinition == null) {
if (other.objectDefinition != null)
return false;
}
else if (!objectDefinition.equals(other.objectDefinition))
return false;
if (order != other.order)
return false;
if (parameterName == null) {
if (other.parameterName != null)
return false;
}
else if (!parameterName.equals(other.parameterName))
return false;
if (parameterType == null) {
if (other.parameterType != null)
return false;
}
else if (!parameterType.equals(other.parameterType))
return false;
if (sqlName == null) {
if (other.sqlName != null)
return false;
}
else if (!sqlName.equals(other.sqlName))
return false;
return true;
}
private static Map<String, SqlParameterDefinition> getSqlBeanParametersFromJsonCreatorConstructor(Constructor<?> c, SqlObjectConfig config) {
Map<String, SqlParameterDefinition> parameters = new LinkedHashMap<String, SqlParameterDefinition>();
Annotation[][] aas = c.getParameterAnnotations();
Class<?>[] pts = c.getParameterTypes();
if (aas == null || aas.length == 0) {
return parameters;
}
for (int i = 0; i < aas.length; i++) {
Annotation[] as = aas[i];
Class<?> parameterType = pts[i];
for (int j = 0; j < as.length; j++) {
Annotation a = as[j];
if (JsonProperty.class.equals(a.annotationType())) {
JsonProperty p = (JsonProperty) a;
String value = p.value();
final SqlParameterDefinition definition = parameterDef(config, c.getDeclaringClass(), value, parameterType, i);
parameters.put(value, definition);
}
}
}
return parameters;
}
private static Map<String, SqlParameterDefinition> getSqlBeanParametersFromConstructorProperties(Constructor<?> c, SqlObjectConfig config) {
Map<String, SqlParameterDefinition> parameters = new LinkedHashMap<String, SqlParameterDefinition>();
String[] constructorProperties = c.getAnnotation(ConstructorProperties.class).value();
Class<?>[] pts = c.getParameterTypes();
for (int i = 0; i < pts.length; i++) {
parameters.put(constructorProperties[i], parameterDef(config, c.getDeclaringClass(), constructorProperties[i], pts[i], i));
}
return parameters;
}
}