/** * 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; } }