/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * 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 org.seasar.extension.jdbc.meta; import java.lang.reflect.Field; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; import org.seasar.extension.jdbc.EntityMeta; import org.seasar.extension.jdbc.EntityMetaFactory; import org.seasar.extension.jdbc.JoinColumnMeta; import org.seasar.extension.jdbc.PropertyMeta; import org.seasar.extension.jdbc.PropertyMetaFactory; import org.seasar.extension.jdbc.RelationshipType; import org.seasar.extension.jdbc.TableMeta; import org.seasar.extension.jdbc.TableMetaFactory; import org.seasar.extension.jdbc.exception.FieldDuplicatedRuntimeException; import org.seasar.extension.jdbc.exception.JoinColumnAutoConfigurationRuntimeException; import org.seasar.extension.jdbc.exception.JoinColumnNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.ManyToOneFKNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.MappedByNotIdenticalRuntimeException; import org.seasar.extension.jdbc.exception.MappedByPropertyNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.NonEntityRuntimeException; import org.seasar.extension.jdbc.exception.OneToOneFKNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.ReferencedColumnNameNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.UnsupportedInheritanceRuntimeException; import org.seasar.framework.container.annotation.tiger.Binding; import org.seasar.framework.container.annotation.tiger.BindingType; import org.seasar.framework.container.annotation.tiger.InitMethod; import org.seasar.framework.convention.PersistenceConvention; import org.seasar.framework.util.ArrayMap; import org.seasar.framework.util.ClassUtil; import org.seasar.framework.util.Disposable; import org.seasar.framework.util.DisposableUtil; import org.seasar.framework.util.ModifierUtil; import org.seasar.framework.util.StringUtil; /** * @author higa * */ public class EntityMetaFactoryImpl implements EntityMetaFactory { /** * エンティティメタデータのマップです。 */ protected ConcurrentHashMap<String, EntityMeta> entityMetaMap = new ConcurrentHashMap<String, EntityMeta>( 200); /** * テーブルメタデータのファクトリです。 */ protected TableMetaFactory tableMetaFactory; /** * プロパティメタデータのファクトリです。 */ protected PropertyMetaFactory propertyMetaFactory; /** * 初期化されたかどうかです。 */ protected volatile boolean initialized; /** * 永続化層の規約です。 */ protected PersistenceConvention persistenceConvention; public EntityMeta getEntityMeta(Class<?> entityClass) { if (entityClass == null) { throw new NullPointerException("entityClass"); } if (!initialized) { initialize(); } EntityMeta entityMeta = getEntityMetaInternal(entityClass); if (!entityMeta.isRelationshipResolved()) { synchronized (entityMeta) { if (!entityMeta.isRelationshipResolved()) { resolveRelationship(entityMeta); } } } return entityMeta; } /** * 内部的に使われるエンティティメタデータを返すメソッドです。 * * @param entityClass * エンティティクラス * @return エンティティメタデータ */ protected EntityMeta getEntityMetaInternal(Class<?> entityClass) { String className = entityClass.getName(); EntityMeta entityMeta = entityMetaMap.get(className); if (entityMeta != null) { return entityMeta; } entityMeta = createEntityMeta(entityClass); EntityMeta entityMeta2 = entityMetaMap.putIfAbsent(className, entityMeta); return entityMeta2 != null ? entityMeta2 : entityMeta; } /** * エンティティメタデータを作成します。 * * @param entityClass * エンティティクラス * @return エンティティメタデータ * @throws NonEntityRuntimeException * クラスがエンティティではない場合。 */ protected EntityMeta createEntityMeta(Class<?> entityClass) throws NonEntityRuntimeException { Entity entity = entityClass.getAnnotation(Entity.class); if (entity == null) { throw new NonEntityRuntimeException(entityClass); } EntityMeta entityMeta = new EntityMeta(); doEntityClass(entityMeta, entityClass); doName(entityMeta, entityClass, entity); doTableMeta(entityMeta, entityClass); doPropertyMeta(entityMeta, entityClass); doCustomize(entityMeta, entityClass); return entityMeta; } /** * エンティティクラスを処理します。 * * @param entityMeta * エンティティメタデータ * @param entityClass * エンティティクラス */ protected void doEntityClass(EntityMeta entityMeta, Class<?> entityClass) { entityMeta.setEntityClass(entityClass); } /** * 名前を処理します。 * * @param entityMeta * エンティティメタデータ * @param entityClass * エンティティクラス * @param entity * エンティティアノテーション */ protected void doName(EntityMeta entityMeta, Class<?> entityClass, Entity entity) { if (!StringUtil.isEmpty(entity.name())) { entityMeta.setName(entity.name()); } else { String entityName = fromClassToEntityName(entityClass); entityMeta.setName(entityName); } } /** * クラスをエンティティ名に変換します。 * * @param entityClass * エンティティクラス * @return エンティティ名 */ protected String fromClassToEntityName(Class<?> entityClass) { return ClassUtil.getShortClassName(entityClass); } /** * テーブルメタデータを処理します。 * * @param entityMeta * エンティティメタデータ * @param entityClass * エンティティクラス */ protected void doTableMeta(EntityMeta entityMeta, Class<?> entityClass) { TableMeta tableMeta = tableMetaFactory.createTableMeta(entityClass, entityMeta); entityMeta.setTableMeta(tableMeta); } /** * プロパティメタデータを処理します。 * * @param entityMeta * エンティティメタデータ * @param entityClass * エンティティクラス */ protected void doPropertyMeta(EntityMeta entityMeta, Class<?> entityClass) { Field[] fields = getFields(entityClass); for (Field f : fields) { f.setAccessible(true); entityMeta.addPropertyMeta(propertyMetaFactory.createPropertyMeta( f, entityMeta)); } } /** * フィールドの配列を返します。 * * @param entityClass * エンティティクラス * @return フィールドの配列 */ protected Field[] getFields(Class<?> entityClass) { ArrayMap fields = new ArrayMap(); for (Field f : ClassUtil.getDeclaredFields(entityClass)) { if (!ModifierUtil.isInstanceField(f)) { continue; } fields.put(f.getName(), f); } for (Class<?> clazz = entityClass.getSuperclass(); clazz != Object.class; clazz = clazz .getSuperclass()) { if (clazz.isAnnotationPresent(Entity.class)) { throw new UnsupportedInheritanceRuntimeException(entityClass); } if (clazz.isAnnotationPresent(MappedSuperclass.class)) { for (Field f : ClassUtil.getDeclaredFields(clazz)) { if (!ModifierUtil.isInstanceField(f)) { continue; } String name = f.getName(); if (!fields.containsKey(name)) { fields.put(name, f); } else { throw new FieldDuplicatedRuntimeException(f); } } } } return (Field[]) fields.toArray(new Field[fields.size()]); } /** * カスタマイズします。 * * @param entityMeta * エンティティメタデータ * @param entityClass * エンティティクラス */ @SuppressWarnings("unused") protected void doCustomize(EntityMeta entityMeta, Class<?> entityClass) { } /** * 関連を解決します。 * * @param entityMeta * エンティティメタデータ */ protected void resolveRelationship(EntityMeta entityMeta) { int size = entityMeta.getPropertyMetaSize(); for (int i = 0; i < size; i++) { PropertyMeta propertyMeta = entityMeta.getPropertyMeta(i); if (!propertyMeta.isRelationship()) { continue; } if (propertyMeta.getMappedBy() != null) { checkMappedBy(propertyMeta, entityMeta); } if (propertyMeta.getRelationshipType() == RelationshipType.MANY_TO_ONE || propertyMeta.getRelationshipType() == RelationshipType.ONE_TO_ONE && propertyMeta.getMappedBy() == null) { resolveJoinColumn(entityMeta, propertyMeta); } } entityMeta.setRelationshipResolved(true); } /** * mappedByで指定されたプロパティが存在するかチェックします。 * * @param propertyMeta * プロパティメタデータ * @param entityMeta * エンティティメタデータ */ protected void checkMappedBy(PropertyMeta propertyMeta, EntityMeta entityMeta) { String mappedBy = propertyMeta.getMappedBy(); Class<?> relationshipClass = propertyMeta.getRelationshipClass(); EntityMeta relationshipEntityMeta = getEntityMetaInternal(relationshipClass); if (relationshipEntityMeta.hasPropertyMeta(mappedBy)) { Field f = relationshipEntityMeta.getPropertyMeta(mappedBy) .getField(); if (entityMeta.getEntityClass() != f.getType()) { throw new MappedByNotIdenticalRuntimeException(entityMeta .getName(), propertyMeta.getName(), propertyMeta .getMappedBy(), entityMeta.getEntityClass(), propertyMeta.getRelationshipClass(), propertyMeta .getMappedBy(), f.getType()); } } else { throw new MappedByPropertyNotFoundRuntimeException(entityMeta .getName(), propertyMeta.getName(), propertyMeta .getMappedBy(), propertyMeta.getRelationshipClass()); } } /** * 関連のJoinColumnを解決します。 * * @param entityMeta * エンティティメタデータ * @param propertyMeta * プロパティメタデータ */ protected void resolveJoinColumn(EntityMeta entityMeta, PropertyMeta propertyMeta) { List<JoinColumnMeta> jcmList = propertyMeta.getJoinColumnMetaList(); EntityMeta inverseEntityMeta = getEntityMetaInternal(propertyMeta .getRelationshipClass()); if (jcmList.size() == 0) { if (inverseEntityMeta.getIdPropertyMetaList().size() != 1) { throw new JoinColumnNotFoundRuntimeException(entityMeta .getName(), propertyMeta.getName()); } propertyMeta.addJoinColumnMeta(new JoinColumnMeta()); } if (jcmList.size() == 1) { JoinColumnMeta joinColumnMeta = jcmList.get(0); PropertyMeta inverseIdPropertyMeta = null; if (inverseEntityMeta.getIdPropertyMetaList().size() == 1) { inverseIdPropertyMeta = inverseEntityMeta .getIdPropertyMetaList().get(0); } if (joinColumnMeta.getName() == null) { if (inverseIdPropertyMeta == null) { throw new JoinColumnAutoConfigurationRuntimeException( entityMeta.getName(), propertyMeta.getName(), inverseEntityMeta.getName()); } joinColumnMeta .setName(persistenceConvention .fromPropertyNameToColumnName(propertyMeta .getName()) + "_" + inverseIdPropertyMeta.getColumnMeta() .getName()); } if (joinColumnMeta.getReferencedColumnName() == null) { if (inverseIdPropertyMeta == null) { throw new JoinColumnAutoConfigurationRuntimeException( entityMeta.getName(), propertyMeta.getName(), inverseEntityMeta.getName()); } joinColumnMeta.setReferencedColumnName(inverseIdPropertyMeta .getColumnMeta().getName()); } } for (JoinColumnMeta jcm : jcmList) { if (!entityMeta.hasColumnPropertyMeta(jcm.getName())) { if (propertyMeta.getRelationshipType() == RelationshipType.MANY_TO_ONE) { throw new ManyToOneFKNotFoundRuntimeException(entityMeta .getName(), propertyMeta.getName(), jcm.getName()); } throw new OneToOneFKNotFoundRuntimeException(entityMeta .getName(), propertyMeta.getName(), jcm.getName()); } if (!inverseEntityMeta.hasColumnPropertyMeta(jcm .getReferencedColumnName())) { throw new ReferencedColumnNameNotFoundRuntimeException( entityMeta.getName(), propertyMeta.getName(), inverseEntityMeta.getName(), jcm .getReferencedColumnName()); } } } /** * テーブルメタデータファクトリを設定します。 * * @param tableMetaFactory * テーブルメタデータファクトリ */ @Binding(bindingType = BindingType.MUST) public void setTableMetaFactory(TableMetaFactory tableMetaFactory) { this.tableMetaFactory = tableMetaFactory; } /** * プロパティメタデータファクトリを設定します。 * * @param propertyMetaFactory * プロパティメタデータファクトリ */ @Binding(bindingType = BindingType.MUST) public void setPropertyMetaFactory(PropertyMetaFactory propertyMetaFactory) { this.propertyMetaFactory = propertyMetaFactory; } /** * 永続化層の規約を設定します * * @param persistenceConvention * 永続化層の規約 */ @Binding(bindingType = BindingType.MUST) public void setPersistenceConvention( PersistenceConvention persistenceConvention) { this.persistenceConvention = persistenceConvention; } /** * 初期化を行ないます。 */ @InitMethod public void initialize() { DisposableUtil.add(new Disposable() { public void dispose() { clear(); } }); initialized = true; } /** * キャッシュをクリアします。 */ public void clear() { entityMetaMap.clear(); initialized = false; } }